--                                                                    --
--  package                         Copyright (c)  Dmitry A. Kazakov  --
--     GNAT.Sockets.Connection_State_Machine       Luebeck            --
--  Implementation                                 Winter, 2012       --
--                                                                    --
--                                Last revision :  18:40 23 Oct 2021  --
--                                                                    --
--  This  library  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. This 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  --
--  General  Public  License  for  more  details.  You  should  have  --
--  received  a  copy  of  the GNU General Public License along with  --
--  this library; if not, write to  the  Free  Software  Foundation,  --
--  Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.    --
--                                                                    --
--  As a special exception, if other files instantiate generics from  --
--  this unit, or you link this unit with other files to produce  an  --
--  executable, this unit does not by  itself  cause  the  resulting  --
--  executable to be covered by the GNU General Public License. This  --
--  exception  does not however invalidate any other reasons why the  --
--  executable file might be covered by the GNU Public License.       --
--____________________________________________________________________--

with Ada.Exceptions;         use Ada.Exceptions;
with Ada.IO_Exceptions;      use Ada.IO_Exceptions;
with Ada.Tags;               use Ada.Tags;
with Strings_Edit.Integers;  use Strings_Edit.Integers;

with Ada.Unchecked_Deallocation;
with Generic_Map;
with System.Address_Image;
with System.Address_To_Access_Conversions;

package body GNAT.Sockets.Connection_State_Machine is

   procedure Check_Initialization_Stream
             (  Item   : Data_Item'Class;
                Stream : Root_Stream_Type'Class
             )  is
   begin
      if Stream not in Initialization_Stream'Class then
         Raise_Exception
         (  Use_Error'Identity,
            (  "Stream attribute of "
            &  Expanded_Name (Item'Tag)
            &  " used with a non-initialization stream "
            &  Expanded_Name (Stream'Tag)
         )  );
      end if;
   end Check_Initialization_Stream;

   procedure Generic_Dump_Stream
             (  Stream : Initialization_Stream'Class;
                Prefix : String := ""
             )  is
         use Strings_Edit;
         Trailer : constant String (Prefix'Range) := (others => ' ');
   begin
      Put_Line
      (  Prefix
      &  "stream at "
      &  System.Address_Image (Stream'Address)
      &  " items: "
      &  Image (Integer (Stream.Count))
      &  ", to ignore next: "
      &  Image (Integer (Stream.Ignore))
      );
      for Index in 1..Stream.Count loop
         Put_Line
         (  Trailer
         &  Image (Integer (Index))
         &  " "
         &  Ada.Tags.Expanded_Name (Get (Stream.Data, Index).all'Tag)
         &  " at "
         &  System.Address_Image (Get (Stream.Data, Index).all'Address)
         );
      end loop;
   end Generic_Dump_Stream;

   package body Generic_Dump is
      procedure Put is new Generic_Dump_Stream;
      procedure Put
                (  Item   : Data_Item'Class;
                   Prefix : String := ""
                )  is
      begin
         Put_Line
         (  Prefix
         &  Ada.Tags.Expanded_Name (Item'Tag)
         &  " at "
         &  System.Address_Image (Item'Address)
         );
         declare
            Nested : constant Data_Item_Ptr_Array := Get_Children (Item);
         begin
            if Nested'Length > 0 then
               declare
                  Trailer : constant String (Prefix'Range) :=
                                            (others => ' ');
               begin
                  for Index in Nested'Range loop
                     Put
                     (  Nested (Index).all,
                        Trailer & "   " & Image (Integer (Index)) & " "
                     );
                  end loop;
               end;
            end if;
         end;
      end Put;

      procedure Put
                (  Buffer : in out External_String_Buffer;
                   Prefix : String
                )  is
         Trailer : constant String (Prefix'Range) := (others => ' ');
         This    : Allocator_Data_Ptr := Buffer.Allocators;
      begin
         Put_Line
         (  Prefix
         &  "external buffer at "
         &  System.Address_Image (Buffer'Address)
         &  " allocators: "
         );
         for Index in Positive'Range loop
            exit when This = null;
            Put
            (  Trailer
            &  "   "
            &  Image (Index)
            &  " at "
            &  System.Address_Image (This.all'Address)
            );
            Put_Line
            (  " -> "
            &  Ada.Tags.Expanded_Name (This.Allocator.all'Tag)
            &  " at "
            &  System.Address_Image (This.Allocator.all'Address)
            );
            This := This.Previous;
         end loop;
      end Put;

      procedure Put_Stream
                (  Item   : Data_Item'Class;
                   Prefix : String := ""
                )  is
         Buffer : aliased External_String_Buffer (64);
         Stream : aliased Initialization_Stream;
      begin
         Stream.Shared := Buffer'Unchecked_Access;
         Data_Item'Class'Write (Stream'Access, Item);
         Put (Stream, Prefix);
      end Put_Stream;

      procedure Put_Call_Stack
                (  Client : State_Machine'Class;
                   Prefix : String := ""
                )  is
         Depth : Natural := 0;
         This  : Sequence_Ptr := Client.Data;
      begin
         while This /= null loop
            Depth := Depth + 1;
            This  := This.Caller;
         end loop;
         declare
            Stack : Sequence_Array (1..Depth);
            procedure Dump
                      (  Start  : Positive;
                         Prefix : String
                      )  is
               This : Sequence renames Stack (Start).all;
               List : Data_Item_Ptr_Array renames This.List;
            begin
               for Index in List'Range loop
                  if Index = This.Current then
                     Put_Line
                     (  Prefix
                     &  "->"
                     &  Image (Integer (Index))
                     &  " "
                     &  Ada.Tags.Expanded_Name (List (Index).all'Tag)
                     );
                     if Start < Stack'Last then
                        Dump (Start + 1, Prefix & "   ");
                     end if;
                  else
                     Put_Line
                     (  Prefix
                     &  "  "
                     &  Image (Integer (Index))
                     &  " "
                     &  Ada.Tags.Expanded_Name (List (Index).all'Tag)
                     );
                  end if;
               end loop;
            end Dump;
         begin
            This := Client.Data;
            for Index in reverse Stack'Range loop
               Stack (Index) := This;
               This  := This.Caller;
            end loop;
            Put_Line (Prefix & Ada.Tags.Expanded_Name (Client'Tag));
            Dump (1, Prefix & "   ");
         end;
      end Put_Call_Stack;
   end Generic_Dump;

   package Address_Maps is
      new Generic_Map
          (  Key_Type    => System.Address,
             Object_Type => Natural,
             "<"         => System."<",
             "="         => System."="
          );
   Prefixes : Address_Maps.Map;

   function Get_Prefix
            (  Stream : Root_Stream_Type'Class
            )  return String is
      use Address_Maps;
      Offset : constant Integer := Find (Prefixes, Stream'Address);
   begin
      if Offset > 0 then
         declare
            Result : constant String (1..Get (Prefixes, Offset)) :=
                              (others => ' ');
         begin
            return Result & System.Address_Image (Stream'Address) & ' ';
         end;
      else
         return "";
      end if;
   end Get_Prefix;

   procedure Set_Prefix
             (  Stream : Root_Stream_Type'Class;
                Prefix : Natural
             )  is
      use Address_Maps;
   begin
      Replace (Prefixes, Stream'Address, Prefix);
   end Set_Prefix;

   procedure Add
             (  Stream : in out Initialization_Stream;
                Item   : Data_Item'Class;
                Nested : Data_Item_Offset := 0
             )  is
   begin
      if Stream.Ignore > 0 then
         Stream.Ignore := Stream.Ignore - 1;
      else
         Stream.Count := Stream.Count + 1;
         Put (Stream.Data, Stream.Count, Self (Item));
      end if;
      Stream.Ignore := Stream.Ignore + Nested;
   end Add;

   procedure Allocate
             (  Pool      : in out Arena_Pool;
                Address   : out System.Address;
                Size      : Storage_Count;
                Alignment : Storage_Count
             )  is
      Size_In_Characters : constant Storage_Count :=
                  (  (Size * System.Storage_Unit + Character'Size - 1)
                  /  Character'Size
                  );
      Length : Natural renames Pool.Parent.Length;
      Buffer : String  renames Pool.Parent.Buffer;
      Last   : Integer;
   begin
Align_To_The_Margin :
      for Index in 1..Alignment loop
         exit when Length >= Buffer'Last;
         if Buffer (Length + 1)'Address mod Alignment = 0 then
            Last := Length + Natural (Size_In_Characters);
            loop
               exit Align_To_The_Margin when Last >= Buffer'Last;
               exit when (  (  Buffer (Last   + 1)'Address
                            -  Buffer (Length + 1)'Address
                            )
                         <= Size
                         );
               Last := Last + 1;
            end loop;
            Address := Buffer (Length + 1)'Address;
            Length  := Last + 1;
            Pool.Parent.Count := Pool.Parent.Count + 1;
            return;
         end if;
         Length := Length + 1;
      end loop Align_To_The_Margin;
      Raise_Exception
      (  Storage_Error'Identity,
         (  "Arena pool exhausted, "
         &  Image (Length)
         &  " used out of "
         &  Image (Buffer'Last)
         &  ", items allocated,"
         &  Storage_Count'Image (Size)
         &  " requested"
      )  );
   end Allocate;

   procedure Call
             (  Client  : in out State_Machine;
                Pointer : Stream_Element_Offset;
                Data    : Sequence_Ptr;
                State   : Stream_Element_Offset := 0
             )  is
   begin
      Client.Fed   := Client.Fed + Unsigned_64 (Pointer - Client.Start);
      Client.Start := Pointer;
      Data.Caller  := Client.Data;
      Client.Data  := Data;
      Data.Current := 0;
      Data.State   := State;
   end Call;

   procedure Clear (Client : in out State_Machine) is
   begin
      Clear (Connection (Client));
      Client.State := 0;
      Client.Start := 0;
      if Client.Data /= null then
         loop
            Client.Data.Current := 1;
            Client.Data.State   := 0;
            exit when Client.Data.Caller = null;
            Client.Data := Client.Data.Caller;
         end loop;
      end if;
   end Clear;

   procedure Connected (Client : in out State_Machine) is
      Stream : aliased Initialization_Stream;
   begin
      if not Client.Once then
         Client.Once := True;
         Initialize (Connection (Client));
         State_Machine'Class'Write
         (  Stream'Access,
            State_Machine'Class (Client)
         );
         Free (Client.Data);
         if Stream.Count > 0 then
            Client.Data :=
               new Sequence'
                   (  Length  => Stream.Count,
                      Caller  => null,
                      State   => 0,
                      Current => 1,
                      List    => Stream.Data.Vector (1..Stream.Count)
                   );
         end if;
      end if;
   end Connected;

   procedure Deallocate
             (  Pool      : in out Arena_Pool;
                Address   : System.Address;
                Size      : Storage_Count;
                Alignment : Storage_Count
             )  is
   begin
      null;
   end Deallocate;

   procedure End_Of_Subsequence
             (  Item    : in out Data_Item;
                Data    : Stream_Element_Array;
                Pointer : Stream_Element_Offset;
                Client  : in out State_Machine'Class;
                State   : in out Stream_Element_Offset
             )  is
   begin
      null;
   end End_Of_Subsequence;

   procedure Enumerate
             (  Stream : access Root_Stream_Type'Class;
                Pool   : Arena_Pool
             )  is
   begin
      null;
   end Enumerate;

   procedure Enumerate
             (  Stream : access Root_Stream_Type'Class;
                Item   : State_Machine
             )  is
   begin
      null;
   end Enumerate;

   procedure Enumerate
             (  Stream : access Root_Stream_Type'Class;
                Item   : Data_Block
             )  is
   begin
      Check_Initialization_Stream (Item, Stream.all);
      declare
         Count : Data_Item_Offset := 0;
         Block : Data_Block'Class renames Self (Item).all;
         This  : Initialization_Stream'Class renames
                 Initialization_Stream'Class (Stream.all);
      begin
         if not Item.Initialized then
            declare
               Counter : aliased Initialization_Stream;
            begin
               if This.Parent = null then
                  Counter.Parent := This.Shared'Access;
               else
                  Counter.Parent := This.Parent;
               end if;
               Block.Initialized := True;
               Data_Block'Class'Write (Counter'Access, Block);
               if Counter.Count <= 1 then
                  Raise_Exception
                  (  Use_Error'Identity,
                     (  "Block "
                     &  Expanded_Name (Block'Tag)
                     &  " is empty (contains no items)"
                  )  );
               end if;
               Self (Item).Data :=
                  new Sequence'
                      (  Length  => Counter.Count - 1,
                         Caller  => null,
                         State   => 0,
                         Current => 1,
                         List    => Counter.Data.Vector
                                    (  2
                                    .. Counter.Count
                      )             );
               Count := Data_Item_Offset (Get_Size (Item)) - 1;
            end;
         end if;
         Add (This, Item, Count);
      end;
   end Enumerate;

   procedure Enumerate
             (  Stream : access Root_Stream_Type'Class;
                Item   : Data_Item
             )  is
   begin
      Check_Initialization_Stream (Item, Stream.all);
      declare
         This : Initialization_Stream'Class renames
                Initialization_Stream'Class (Stream.all);
      begin
         Add (This, Item);
      end;
   end Enumerate;

   procedure Enumerate
             (  Stream : access Root_Stream_Type'Class;
                Item   : Data_Selector
             )  is
   begin
      Check_Initialization_Stream (Item, Stream.all);
      declare
         Selector : Data_Selector'Class renames
                    Self (Data_Selector'Class (Item)).all;
         Count    : Data_Item_Offset := 0;
         This     : Initialization_Stream'Class renames
                    Initialization_Stream'Class (Stream.all);
      begin
         if not Selector.Initialized then
            Selector.Initialized := True;
            declare
               Counter : aliased Initialization_Stream;
            begin
               if This.Parent = null then
                  Counter.Parent := This.Shared'Access;
               else
                  Counter.Parent := This.Parent;
               end if;
               Selector.Initialized := True;
               Data_Selector'Class'Write (Counter'Access, Selector);
               if Counter.Count <= 1 then
                  Raise_Exception
                  (  Use_Error'Identity,
                     (  "Selector "
                     &  Expanded_Name (Data_Item'Class (Item)'Tag)
                     &  " contains no alternatives"
                  )  );
               end if;
               Selector.Alternatives :=
                  new Alternatives_List
                      (  Natural (Counter.Count)
                      -  1
                      );
               declare
                  This : Data_Item_Ptr;
                  List : Sequence_Array renames
                         Item.Alternatives.List;
               begin
                  for Index in List'Range loop
                     This :=
                        Get
                        (  Counter.Data,
                           Data_Item_Offset (Index) + 1
                        );
                     List (Index) :=
                        new Sequence'
                            (  Length  => 1,
                               Caller  => null,
                               State   => 0,
                               Current => 1,
                               List    => (1 => This)
                            );
                  end loop;
               end;
               Count := Data_Item_Offset (Get_Size (Item)) - 1;
            end;
         end if;
         Add (This, Item, Count);
      end;
   end Enumerate;

   procedure Enumerate
             (  Stream : access Root_Stream_Type'Class;
                Item   : Shared_Data_Item
             )  is
   begin
      Check_Initialization_Stream (Item, Stream.all);
      declare
         This : Initialization_Stream'Class renames
                Initialization_Stream'Class (Stream.all);
      begin
         if not Item.Initialized then
            declare
               Shared : Shared_Data_Item'Class renames
                        Self (Item).all;
            begin
               Shared.Initialized := True;
               Shared.Previous    := This.Shared;
               This.Shared        := Shared'Unchecked_Access;
            end;
         end if;
         Add (This, Item);
      end;
   end Enumerate;

   procedure Erase (Buffer : in out External_String_Buffer) is
      This : Allocator_Data_Ptr := Buffer.Allocators;
      Next : Allocator_Data_Ptr;
   begin
      while This /= null loop
         Next := This.Previous;
         This.Previous := null;
         Freed (This.Allocator.all);
         This := Next;
      end loop;
      Buffer.Length := 0;
      Buffer.Count  := 0;
      Buffer.Allocators := null;
   end Erase;

   procedure External_Initialize
             (  Item   : Data_Item'Class;
                Shared : Shared_Data_Item_Ptr := null
             )  is
      Stream : aliased Initialization_Stream;
   begin
      Stream.Shared := Shared;
      Data_Item'Class'Write (Stream'Access, Item);
   end External_Initialize;

   procedure Feed
             (  Item    : in out Shared_Data_Item;
                Data    : Stream_Element_Array;
                Pointer : in out Stream_Element_Offset;
                Client  : in out State_Machine'Class;
                State   : in out Stream_Element_Offset
             )  is
   begin
      State := 0;
   end Feed;

   procedure Feed
             (  Item    : in out External_String_Buffer;
                Data    : Stream_Element_Array;
                Pointer : in out Stream_Element_Offset;
                Client  : in out State_Machine'Class;
                State   : in out Stream_Element_Offset
             )  is
   begin
      Erase (Item); -- Erase the buffer
      Feed (Shared_Data_Item (Item), Data, Pointer, Client, State);
   end Feed;

   procedure Feed
             (  Item    : in out Data_Block;
                Data    : Stream_Element_Array;
                Pointer : in out Stream_Element_Offset;
                Client  : in out State_Machine'Class;
                State   : in out Stream_Element_Offset
             )  is
   begin
      Call (Client, Pointer, Item.Data);
      State := 0;
   end Feed;

   procedure Feed
             (  Item    : in out Data_Null;
                Data    : Stream_Element_Array;
                Pointer : in out Stream_Element_Offset;
                Client  : in out State_Machine'Class;
                State   : in out Stream_Element_Offset
             )  is
   begin
      State := 0;
   end Feed;

   procedure Feed
             (  Item    : in out Data_Selector;
                Data    : Stream_Element_Array;
                Pointer : in out Stream_Element_Offset;
                Client  : in out State_Machine'Class;
                State   : in out Stream_Element_Offset
             )  is
   begin
      Call (Client, Pointer, Item.Alternatives.List (Item.Current));
      State := 0;
   end Feed;

   procedure Finalize (Client : in out State_Machine) is
   begin
      if Client.Data /= null then
         while Client.Data.Caller /= null loop
            Client.Data := Client.Data.Caller;
         end loop;
         Free (Client.Data);
      end if;
      Finalize (Connection (Client));
   end Finalize;

   procedure Finalize (Item : in out Data_Block) is
   begin
      Free (Item.Data);
   end Finalize;

   procedure Finalize (Item : in out Data_Selector) is
      procedure Free is
         new Ada.Unchecked_Deallocation
             (  Alternatives_List,
                Alternatives_List_Ptr
             );
   begin
      if Item.Alternatives /= null then
         declare
            List : Sequence_Array renames Item.Alternatives.List;
         begin
            for Index in List'Range loop
               Free (List (Index));
            end loop;
         end;
         Free (Item.Alternatives);
      end if;
   end Finalize;

   procedure Finalize (Buffer : in out External_String_Buffer) is
   begin
      Erase (Buffer);
      Buffer.Allocators := null;
      Finalize (Shared_Data_Item (Buffer));
   end Finalize;

   procedure Freed (Item : in out Data_Item) is
   begin
      null;
   end Freed;

   package From_Block_Address is
      new System.Address_To_Access_Conversions (Data_Block'Class);

   package From_Item_Address is
      new System.Address_To_Access_Conversions (Data_Item'Class);

   package From_Selector_Address is
      new System.Address_To_Access_Conversions (Data_Selector'Class);

   package From_Shared_Item_Address is
      new System.Address_To_Access_Conversions (Shared_Data_Item'Class);

   function Get_Alternative (Item : Data_Selector) return Positive is
   begin
      return Item.Current;
   end Get_Alternative;

   function Get_Alternatives_Number (Item : Data_Selector)
      return Positive is
   begin
      if not Item.Initialized or else Item.Alternatives = null then
         Raise_Exception
         (  Use_Error'Identity,
            (  "Selector "
            &  Expanded_Name (Data_Item'Class (Item)'Tag)
            &  " was not properly initialized"
         )  );
      end if;
      return Positive (Item.Alternatives.Size);
   end Get_Alternatives_Number;

   function Get_Children
            (  Item : Data_Item
            )  return Data_Item_Ptr_Array is
   begin
      return (1..0 => null);
   end Get_Children;

   function Get_Children
            (  Item : Data_Block
            )  return Data_Item_Ptr_Array is
      Length : Data_Item_Offset  := 0;
   begin
      if not Item.Initialized or else Item.Data = null then
         Raise_Exception
         (  Use_Error'Identity,
            (  "Block "
            &  Expanded_Name (Data_Item'Class (Item)'Tag)
            &  " was not properly initialized"
         )  );
      end if;
      return Item.Data.List;
   end Get_Children;

   function Get_Children
            (  Item : Data_Selector
            )  return Data_Item_Ptr_Array is
      Length : Data_Item_Offset  := 0;
   begin
      if not Item.Initialized or else Item.Alternatives = null then
         Raise_Exception
         (  Use_Error'Identity,
            (  "Selector "
            &  Expanded_Name (Data_Item'Class (Item)'Tag)
            &  " was not properly initialized"
         )  );
      end if;
      for Index in Item.Alternatives.List'Range loop
         Length := Length + Item.Alternatives.List (Index).List'Length;
      end loop;
      declare
         Result : Data_Item_Ptr_Array (1..Length);
         No     : Data_Item_Address := 1;
      begin
         for Index in Item.Alternatives.List'Range loop
            declare
               Alternative : Data_Item_Ptr_Array renames
                             Item.Alternatives.List (Index).List;
            begin
               Result (No..No + Alternative'Length - 1) := Alternative;
               No := No + Alternative'Length;
            end;
         end loop;
         return Result;
      end;
   end Get_Children;

   function Get_Length (Item : Data_Block) return Positive is
   begin
      if not Item.Initialized or else Item.Data = null then
         Raise_Exception
         (  Use_Error'Identity,
            (  "Block "
            &  Expanded_Name (Data_Item'Class (Item)'Tag)
            &  " was not properly initialized"
         )  );
      end if;
      return Positive (Item.Data.Length);
   end Get_Length;

   function Get_Size (Item : Data_Block) return Natural is
   begin
      if not Item.Initialized or else Item.Data = null then
         Raise_Exception
         (  Use_Error'Identity,
            (  "Block "
            &  Expanded_Name (Data_Item'Class (Item)'Tag)
            &  " was not properly initialized"
         )  );
      end if;
      declare
         Result : Natural := 1;
         List   : Data_Item_Ptr_Array renames Item.Data.List;
      begin
         for Index in List'Range loop
            Result := Result + Get_Size (List (Index).all);
         end loop;
         return Result;
      end;
   end Get_Size;

   function Get_Size (Item : Data_Item) return Natural is
   begin
      return 1;
   end Get_Size;

   function Get_Size (Item : Data_Selector) return Natural is
   begin
      if not Item.Initialized or else Item.Alternatives = null then
         Raise_Exception
         (  Use_Error'Identity,
            (  "Selector "
            &  Expanded_Name (Data_Item'Class (Item)'Tag)
            &  " was not properly initialized"
         )  );
      end if;
      declare
         Result   : Natural := 1;
         Sequence : Sequence_Array renames Item.Alternatives.List;
      begin
         for Index in Sequence'Range loop
            declare
               List : Data_Item_Ptr_Array renames Sequence (Index).List;
            begin
               for Index in List'Range loop
                  Result := Result + Get_Size (List (Index).all);
               end loop;
            end;
         end loop;
         return Result;
      end;
   end Get_Size;

   procedure Read
             (  Stream : in out Initialization_Stream;
                Item   : out Stream_Element_Array;
                Last   : out Stream_Element_Offset
             )  is
   begin
      Last := Item'Last;
   end Read;

   procedure Received
             (  Client  : in out State_Machine;
                Data    : Stream_Element_Array;
                Pointer : in out Stream_Element_Offset
             )  is
   begin
      Pointer := Data'First;
      while Pointer <= Data'Last loop
         Client.Start := Pointer;
         Feed
         (  Client.Data.List (Client.Data.Current).all,
            Data,
            Pointer,
            Client,
            Client.State
         );
         Client.Fed :=
            Client.Fed + Unsigned_64 (Pointer - Client.Start);
         if Client.State = 0 then -- Done with this item
            Client.Data.Current := Client.Data.Current + 1;
            while Client.Data.Current > Client.Data.Length loop
               if Client.Data.Caller = null then
                  Client.Data.Current := 1;
                  Process_Packet (State_Machine'Class (Client));
                  exit;
               end if;
               Client.State := Client.Data.State; -- Restore state
               Client.Data  := Client.Data.Caller;
               if Client.State /= 0 then
                  End_Of_Subsequence
                  (  Client.Data.List (Client.Data.Current).all,
                     Data,
                     Pointer,
                     Client,
                     Client.State
                  );
                  exit when Client.State /= 0;
               end if;
               Client.Data.Current := Client.Data.Current + 1;
            end loop;
         else
            exit when Pointer > Data'Last; -- All data consumed
            Raise_Exception
            (  Status_Error'Identity,
               (  "Unprocessed data left when after return from "
               &  Expanded_Name
                  (  Client.Data.List
                     (  Client.Data.Current
                     ) .all'Tag
            )  )  );
         end if;
      end loop;
   end Received;

   function Self (Item : Data_Block'Class) return Data_Block_Ptr is
      use From_Block_Address;
   begin
      return To_Pointer (Item'Address).all'Unchecked_Access;
   end Self;

   function Self (Item : Data_Item'Class) return Data_Item_Ptr is
      use From_Item_Address;
   begin
      return To_Pointer (Item'Address).all'Unchecked_Access;
   end Self;

   function Self (Item : Data_Selector'Class)
      return Data_Selector_Ptr is
      use From_Selector_Address;
   begin
      return To_Pointer (Item'Address).all'Unchecked_Access;
   end Self;

   function Self (Item : Shared_Data_Item'Class)
      return Shared_Data_Item_Ptr is
      use From_Shared_Item_Address;
   begin
      return To_Pointer (Item'Address).all'Unchecked_Access;
   end Self;

   procedure Set_Alternative
             (  Item        : in out Data_Selector;
                Alternative : Positive
             )  is
   begin
      if Alternative > Get_Alternatives_Number (Item) then
         Raise_Exception
         (  Constraint_Error'Identity,
            (  "Invalid alternative number of selector "
            &  Expanded_Name (Data_Item'Class (Item)'Tag)
         )  );
      end if;
      Item.Current := Alternative;
   end Set_Alternative;

   function Storage_Size
            (  Pool : Arena_Pool
            )  return Storage_Count is
   begin
      return Pool.Parent.Buffer'Size / System.Storage_Unit;
   end Storage_Size;

   function To_Integer (Value : Unsigned_8) return Integer_8 is
   begin
      if Value < 2**7 then
         return Integer_8 (Value);
      else
         return -Integer_8 (not Value) - 1;
      end if;
   end To_Integer;

   function To_Integer (Value : Unsigned_16) return Integer_16 is
   begin
      if Value < 2**15 then
         return Integer_16 (Value);
      else
         return -Integer_16 (not Value) - 1;
      end if;
   end To_Integer;

   function To_Integer (Value : Unsigned_32) return Integer_32 is
   begin
      if Value < 2**31 then
         return Integer_32 (Value);
      else
         return -Integer_32 (not Value) - 1;
      end if;
   end To_Integer;

   function To_Integer (Value : Unsigned_64) return Integer_64 is
   begin
      if Value < 2**63 then
         return Integer_64 (Value);
      else
         return -Integer_64 (not Value) - 1;
      end if;
   end To_Integer;

   function To_Unsigned (Value : Integer_8) return Unsigned_8 is
   begin
      if Value >= 0 then
         return Unsigned_8 (Value);
      else
         return not Unsigned_8 (-(Value + 1));
      end if;
   end To_Unsigned;

   function To_Unsigned (Value : Integer_16) return Unsigned_16 is
   begin
      if Value >= 0 then
         return Unsigned_16 (Value);
      else
         return not Unsigned_16 (-(Value + 1));
      end if;
   end To_Unsigned;

   function To_Unsigned (Value : Integer_32) return Unsigned_32 is
   begin
      if Value >= 0 then
         return Unsigned_32 (Value);
      else
         return not Unsigned_32 (-(Value + 1));
      end if;
   end To_Unsigned;

   function To_Unsigned (Value : Integer_64) return Unsigned_64 is
   begin
      if Value >= 0 then
         return Unsigned_64 (Value);
      else
         return not Unsigned_64 (-(Value + 1));
      end if;
   end To_Unsigned;

   procedure Write
             (  Stream : in out Initialization_Stream;
                Item   : Stream_Element_Array
             )  is
   begin
      null;
   end Write;

end GNAT.Sockets.Connection_State_Machine;
