--                                                                    --
--  package                         Copyright (c)  Dmitry A. Kazakov  --
--     GNAT.Sockets.Connection_State_Machine.      Luebeck            --
--     ASN1.Strings                                Spring, 2019       --
--  Implementation                                                    --
--                                Last revision :  10:13 29 Nov 2020  --
--                                                                    --
--  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 Strings_Edit.UTF8.Handling;  use Strings_Edit.UTF8.Handling;

with Ada.Unchecked_Conversion;
with Strings_Edit.UTF8.ITU_T61;
with System.Address_To_Access_Conversions;

with GNAT.Sockets.Connection_State_Machine.ASN1.Lengths;
use  GNAT.Sockets.Connection_State_Machine.ASN1.Lengths;

package body GNAT.Sockets.Connection_State_Machine.ASN1.Strings is

   type Embedded_Length is mod 2**24;
   type Embedding_Mode is
        (  String_Body,
           String_Type,
           End_Of_Content
        );
   type Octet_Count is mod 4;
   type Status_Type is record
      Length : Embedded_Length;
      Octet  : Octet_Count;
      Mode   : Embedding_Mode;
   end record;
   for Status_Type use record
      Length at 0 range  0..23;
      Octet  at 0 range 24..28;
      Mode   at 0 range 29..30;
   end record;
   for Status_Type'Size use Unsigned_32'Size;

   function From_State
            (  State : Stream_Element_Offset
            )  return Status_Type is
      function Convert is
         new Ada.Unchecked_Conversion (Unsigned_32, Status_Type);
   begin
      return Convert (Unsigned_32 (State));
   end From_State;

   function To_State
            (  Status : Status_Type
            )  return Stream_Element_Offset is
      function Convert is
         new Ada.Unchecked_Conversion (Status_Type, Unsigned_32);
   begin
      return Stream_Element_Offset (Convert (Status));
   end To_State;

   procedure Not_A_String (Tag : ASN1_Tag) is
   begin
      Raise_Exception
      (  Data_Error'Identity,
         (  "Not an ASN.1 string tag "
         &  Image (Integer (Tag))
      )  );
   end Not_A_String;

   function Check_Code_Point
            (  Code : UTF8_Code_Point;
               Tag  : ASN1_Type
            )  return Boolean is
   begin
      case Tag is
         when Octet_String_Tag      |
              Object_Descriptor_Tag |
              Generalized_Time_Tag  |
              UTC_Time_Tag          =>
            return True;
         when UTF8_String_Tag =>
            return True;
         when Numeric_String_Tag =>
            case Code is
               when Character'Pos (' ') |
                    Character'Pos ('0') .. Character'Pos ('9') =>
                  return False;
               when others =>
                  null;
            end case;
         when Printable_String_Tag =>
            case Code is
               when Character'Pos ('A') .. Character'Pos ('Z') |
                    Character'Pos ('a') .. Character'Pos ('z') |
                    Character'Pos ('0') .. Character'Pos ('9') |
                    Character'Pos (' ') |
                    Character'Pos (''') |
                    Character'Pos ('(') |
                    Character'Pos (')') |
                    Character'Pos ('+') |
                    Character'Pos (',') |
                    Character'Pos ('-') |
                    Character'Pos ('.') |
                    Character'Pos ('/') |
                    Character'Pos (';') |
                    Character'Pos ('=') |
                    Character'Pos ('?') =>
                  return False;
               when others =>
                  null;
            end case;
         when Teletext_String_Tag =>
            declare
               use Strings_Edit.UTF8.ITU_T61;
            begin
               begin
                  if To_ITU_T61 (Code) = 'a' then
                     null;
                  end if;
                  return False;
               exception
                  when Constraint_Error =>
                     null;
               end;
            end;
         when Videotext_String_Tag =>
            return True;
         when IA5_String_Tag =>
            if Code <= Character'Pos ('~') then
               return False;
            end if;
         when Graphic_String_Tag =>
            return True;
         when ISO646_String_Tag =>
            if Code in Character'Pos (' ') .. Character'Pos ('~') then
               return False;
            end if;
         when General_String_Tag =>
            return True;
         when Universal_String_Tag =>
            return True;
         when Character_String_Tag =>
            if Code < 256 then
               return False;
            end if;
         when BMP_String_Tag =>
            if Code < 2**16 then
               return False;
            end if;
         when others =>
            null;
      end case;
      Raise_Exception (Data_Error'Identity, Unsupported);
   end Check_Code_Point;

   procedure Check_Value (Value : String; Tag : ASN1_Type) is
      Code    : UTF8_Code_Point;
      Pointer : Positive := Value'First;
   begin
      while Pointer <= Value'Last loop
         Get (Value, Pointer, Code);
         exit when Check_Code_Point (Code, Tag);
      end loop;
   end Check_Value;

   procedure Check_Value
             (  Parent : ASN1_Data_Item'Class;
                Item   : Embedded_String_Data;
                Tag    : ASN1_Type
             )  is
   begin
      Check_Value (Item.Parent.Value (1..Item.Parent.Length), Tag);
   end Check_Value;

   procedure Check_Value
             (  Parent : ASN1_Data_Item'Class;
                Item   : External_String_Data;
                Tag    : ASN1_Type
             )  is
   begin
      if Item.Buffer = null then
         Uninitialized (Parent);
      end if;
      declare
         Code    : UTF8_Code_Point;
         Buffer  : String renames Item.Buffer.Buffer;
         Pointer : Positive := Item.Start;
         Last    : constant Positive :=
                            Pointer + Item.Length - 1;
      begin
         while Pointer <= Last loop
            Get (Buffer, Pointer, Code);
            exit when Check_Code_Point (Code, Tag);
         end loop;
      end;
   end Check_Value;

   procedure Do_Feed
             (  Item     : in out String_Data'Class;
                Data     : Stream_Element_Array;
                Pointer  : in out Stream_Element_Offset;
                Expected : ASN1_Type_Array;
                State    : in out Stream_Element_Offset
             )  is
      procedure Check is
      begin
         for Index in Expected'Range loop
            if Item.Tag = Expected (Index) then
               return;
            end if;
         end loop;
         Raise_Exception
         (  Data_Error'Identity,
            (  "ASN.1 type "
            &  Image (Item.Tag)
            &  " does not match expected type "
            &  Image (Expected)
         )  );
      end Check;
   begin
      if State = 0 then
         declare
            This : constant Stream_Element := Data (Pointer);
         begin
            Reset (Item);
            Item.Primitive := 0 = (This and 16#20#);
            Item.Nested    := False;
            if Universal_Class /= ASN1_Tag (This and 2#1100_0000#) then
               if Expected'Length = 1 then
                  Raise_Exception
                  (  Data_Error'Identity,
                     "ASN.1 type " & Image (Expected) & " is expected"
                  );
               else
                  Raise_Exception
                  (  Data_Error'Identity,
                     (  "One of ASN.1 types "
                     &  Image (Expected)
                     &  " is expected"
                  )  );
               end if;
            end if;
            if (This and 16#1F#) >= 31 then -- Long tag
               State         := 1;
               Item.Long_Tag := True;
               Item.Tag      := 0;
            else
               Item.Tag      := ASN1_Type (This and 16#1F#);
               Item.Long_Tag := False;
               State         := Start_Length;
               Check;
            end if;
         end;
         Pointer := Pointer + 1;
         if Pointer > Data'Last then
            return;
         end if;
      end if;
      if Item.Long_Tag and then State > 0 then
         loop
            declare
               This : constant ASN1_Type := ASN1_Type (Data (Pointer));
            begin
               Pointer := Pointer + 1;
               if This > 127 then
                  Item.Tag := Item.Tag * 128 + This - 128;
               else
                  Item.Tag := Item.Tag * 128 + This;
                  Item.Long_Tag := False;
                  State := Start_Length;
                  Check;
                  return;
               end if;
            end;
            if Pointer > Data'Last then
               return;
            end if;
         end loop;
      end if;
   end Do_Feed;

   procedure Feed
             (  Parent  : ASN1_Data_Item'Class;
                Item    : in out String_Data'Class;
                Data    : Stream_Element_Array;
                Pointer : in out Stream_Element_Offset;
                Client  : in out State_Machine'Class;
                State   : in out Stream_Element_Offset
             )  is
      Status : Status_Type;

      procedure Encoding_Error is
      begin
         Raise_Exception
         (  Data_Error'Identity,
            (  "Invalid ASN.1 character encoding of "
            &  Image (Integer (Item.Tag))
         )  );
      end Encoding_Error;
   begin
      if State = 0 then
         Reset (Item);
         if Item.Definite then
            declare
               Length : constant Stream_Element_Offset := Item.Last;
            begin
               if Item.Primitive then -- Primitive string
                  if Length = 0 then
                     State := 0;
                     return;
                  else
                     Status.Mode   := String_Body;
                     Status.Length := Embedded_Length (Length);
                     Item.Last     := Pointer + Length - 1;
                  end if;
               else -- The total length is here
                  Item.Last   := Pointer + Length;
                  Status.Mode := String_Type;
               end if;
            end;
         else
            if Item.Primitive then
               Raise_Exception (Data_Error'Identity, Wrong_Length);
            end if;
            Status.Mode   := String_Type;
            Status.Length := 0;
         end if;
         Item.Nested  := True;
         Status.Octet := 0;
         State        := To_State (Status);
      end if;
      while Pointer <= Data'Last loop
         if State < 0 then
            while not Is_Length_Ready (State) loop
               if Pointer > Data'Last then
                  return;
               end if;
               Embedded_Feed (Data, Pointer, State);
            end loop;
            if Is_Implicit (Parent) then
               Item.Primitive := not Is_Indefinite (State);
            end if;
            if Is_Indefinite (State) then
               if Item.Primitive then
                  Raise_Exception
                  (  Data_Error'Identity,
                     "Primitive ASN.1 string has indefinite length"
                  );
               elsif Item.Nested then
                  Raise_Exception (Data_Error'Identity, Nested);
               end if;
               Item.Definite := False;
               Item.Nested   := True;
               Status.Length := 0;
               Status.Mode   := String_Type;
               Status.Octet  := 0;
            else
               declare
                  Length : constant Stream_Element_Offset :=
                                    Get_Length (State);
               begin
                  if Item.Primitive then -- Primitive string
                     if Length = 0 then
                        State := 0;
                        return;
                     else
                        Status.Mode  := String_Body;
                        Status.Octet := 0;
                        Status.Length := Embedded_Length (Length);
                        if (  Integer (Status.Length)
                           >  Get_Available (Item)
                           )  then
                           Raise_Exception
                           (  Data_Error'Identity,
                              Too_Large
                           );
                        end if;
                     end if;
                  elsif Item.Nested then -- Constructor string
                     if Length = 0 then
                        if (  Item.Definite
                           and then
                              Pointer > Item.Last
                           )  then
                           State := 0;
                           return;
                        end if;
                        Status.Mode  := String_Type;
                        Status.Octet := 0;
                     elsif (  Item.Definite
                           and then
                              Pointer > Item.Last
                           )  then
                        Raise_Exception
                        (  Data_Error'Identity,
                           "ASN.1 string data overrun"
                        );
                     else
                        Status.Mode  := String_Body;
                        Status.Octet := 0;
                        Status.Length := Embedded_Length (Length);
                        if (  Integer (Status.Length)
                           >  Get_Available (Item)
                           )  then
                           Raise_Exception
                           (  Data_Error'Identity,
                              Too_Large
                           );
                        end if;
                     end if;
                  else -- Not nested, the total length is here
                     Item.Last     := Pointer + Length - 1;
                     Item.Definite := True;
                     Item.Nested   := True;
                     Status.Mode   := String_Type;
                     Status.Octet  := 0;
                  end if;
               end;
            end if;
            State := To_State (Status);
         else
            Status := From_State (State);
            loop
               case Status.Mode is
                  when String_Body =>
                     loop
                        Status.Length := Status.Length - 1;
                        case Item.Tag is
                           when Embedded_Tag          |
                                Date_Tag              |
                                Date_Time_Tag         |
                                Duration_Tag          |
                                Generalized_Time_Tag  |
                                Object_Descriptor_Tag |
                                Octet_String_Tag      |
                                Time_Of_Day_Tag       |
                                UTC_Time_Tag          =>
                              Store (Item, Data (Pointer));
                           when UTF8_String_Tag =>
                              Store (Item, Data (Pointer));
                           when Numeric_String_Tag =>
                              case Data (Pointer) is
                                 when Character'Pos (' ') |
                                      Character'Pos ('0')
                                   .. Character'Pos ('9') =>
                                    Store (Item, Data (Pointer));
                                 when others =>
                                    Encoding_Error;
                              end case;
                           when Printable_String_Tag =>
                              case Data (Pointer) is
                                 when Character'Pos ('A')
                                   .. Character'Pos ('Z') |
                                      Character'Pos ('a')
                                   .. Character'Pos ('z') |
                                      Character'Pos ('0')
                                   .. Character'Pos ('9') |
                                      Character'Pos (' ') |
                                      Character'Pos (''') |
                                      Character'Pos ('(') |
                                      Character'Pos (')') |
                                      Character'Pos ('+') |
                                      Character'Pos (',') |
                                      Character'Pos ('-') |
                                      Character'Pos ('.') |
                                      Character'Pos ('/') |
                                      Character'Pos (';') |
                                      Character'Pos ('=') |
                                      Character'Pos ('?') =>
                                    Store (Item, Data (Pointer));
                                 when others =>
                                    Encoding_Error;
                              end case;
                           when Teletext_String_Tag =>
                              declare
                                 use Strings_Edit.UTF8.ITU_T61;
                                 Value : Code_Point;
                              begin
                                 begin
                                    Value :=
                                       From_ITU_T61
                                       (  Character'Val
                                          (  Data (Pointer)
                                       )  );
                                 exception
                                    when Constraint_Error =>
                                       Encoding_Error;
                                 end;
                                 Store (Item, Image (Value));
                              end;
                           when Videotext_String_Tag =>
                              Store (Item, Data (Pointer));
                           when IA5_String_Tag =>
                              if Data (Pointer) > 127 then
                                 Encoding_Error;
                              end if;
                              Store (Item, Data (Pointer));
                           when Graphic_String_Tag =>
                              Store (Item, Data (Pointer));
                           when ISO646_String_Tag =>
                              case Data (Pointer) is
                                 when 32..127 =>
                                    Store (Item, Data (Pointer));
                                 when others =>
                                    Encoding_Error;
                              end case;
                           when General_String_Tag =>
                              Store (Item, Data (Pointer));
                           when Universal_String_Tag =>
                              case Status.Octet is
                                 when 0 =>
                                    Item.Accumulator :=
                                       Code_Point (Data (Pointer));
                                    Status.Octet := 1;
                                 when 1 =>
                                    Item.Accumulator :=
                                       (  Item.Accumulator * 256
                                       +  Code_Point (Data (Pointer))
                                       );
                                    Status.Octet := 2;
                                 when 2 =>
                                    Item.Accumulator :=
                                       (  Item.Accumulator * 256
                                       +  Code_Point (Data (Pointer))
                                       );
                                    Status.Octet := 3;
                                 when others =>
                                    begin
                                       Item.Accumulator :=
                                          (  Item.Accumulator * 256
                                          +  Code_Point (Data (Pointer))
                                          );
                                    exception
                                       when Constraint_Error =>
                                          Encoding_Error;
                                    end;
                                    Store
                                    (  Item,
                                       Image (Item.Accumulator)
                                    );
                                    Status.Octet := 0;
                              end case;
                           when Character_String_Tag =>
                              Store
                              (  Item,
                                 To_UTF8
                                 (  Character'Val (Data (Pointer))
                              )  );
                           when BMP_String_Tag =>
                              if Status.Octet = 0 then
                                 Item.Accumulator :=
                                    Code_Point (Data (Pointer));
                                 Status.Octet := 1;
                              else
                                 begin
                                    Item.Accumulator :=
                                       (  Item.Accumulator * 256
                                       +  Code_Point (Data (Pointer))
                                       );
                                 exception
                                    when Constraint_Error =>
                                       Encoding_Error;
                                 end;
                                 Store (Item, Image (Item.Accumulator));
                                 Status.Octet := 0;
                              end if;
                           when others =>
                              Store (Item, Data (Pointer));
                        end case;
                        Pointer := Pointer + 1;
                        if Status.Length = 0 then
                           if Status.Octet /= 0 then
                              Encoding_Error;
                           end if;
                           if (  Item.Primitive
                              or else
                                 (  Item.Definite
                                 and then
                                    Pointer > Item.Last
                              )  )  then
                              State := 0;
                              return;
                           else
                              Status.Mode  := String_Type;
                              Status.Octet := 0;
                              exit;
                           end if;
                        end if;
                        if Pointer > Data'Last then
                           State := To_State (Status);
                           return;
                        end if;
                     end loop;
                  when String_Type =>
                     case Status.Octet is
                        when 0 =>
                           if (  Item.Definite
                              and then
                                 Pointer > Item.Last
                              )  then
                              State := 0;
                              return;
                           end if;
                           if (Data (Pointer) and 16#1F#) >= 31 then
                              Status.Length := 0;
                              Status.Octet  := 1;
                              Pointer       := Pointer + 1;
                              if (  Item.Definite
                                 and then
                                    Pointer > Item.Last
                                 )  then
                                 Raise_Exception
                                 (  Data_Error'Identity,
                                    Invalid_Length
                                 );
                              end if;
                           else -- Check the tag
                              Status.Octet  := 2;
                              Status.Length :=
                                 Embedded_Length
                                 (  Data (Pointer) and 16#1F#
                                 );
                              Pointer := Pointer + 1;
                           end if;
                        when 1 =>
                           if (  Item.Definite
                              and then
                                 Pointer > Item.Last
                              )  then
                              Raise_Exception
                              (  Data_Error'Identity,
                                 Invalid_Length
                              );
                           end if;
                           begin
                              if Data (Pointer) > 127 then
                                 Status.Length :=
                                    (  Status.Length * 128
                                    +  Embedded_Length
                                       (  Data (Pointer)
                                       -  128
                                    )  );
                                 Pointer := Pointer + 1;
                              else
                                 Status.Length :=
                                    (  Status.Length * 128
                                    +  Embedded_Length
                                       (  Data (Pointer)
                                    )  );
                                 Pointer      := Pointer + 1;
                                 Status.Octet := 2;
                              end if;
                           exception
                              when Constraint_Error =>
                                 Raise_Exception
                                 (  Data_Error'Identity,
                                    Tag_Too_Large
                                 );
                           end;
                        when others =>
                           Item.Tag := ASN1_Type (Status.Length);
                           case Item.Tag is
                              when BMP_String_Tag        |
                                   Character_String_Tag  |
                                   Date_Tag              |
                                   Date_Time_Tag         |
                                   Duration_Tag          |
                                   General_String_Tag    |
                                   Generalized_Time_Tag  |
                                   Graphic_String_Tag    |
                                   Embedded_Tag          |
                                   IA5_String_Tag        |
                                   ISO646_String_Tag     |
                                   Numeric_String_Tag    |
                                   Object_Descriptor_Tag |
                                   Octet_String_Tag      |
                                   Printable_String_Tag  |
                                   Teletext_String_Tag   |
                                   Time_Of_Day_Tag       |
                                   Time_Tag              |
                                   Universal_String_Tag  |
                                   UTC_Time_Tag          |
                                   UTF8_String_Tag       |
                                   Videotext_String_Tag  =>
                                 if (  Item.Constraint /= 0
                                    and then
                                       Item.Constraint /= Item.Tag
                                    )  then
                                    Raise_Exception
                                    (  Data_Error'Identity,
                                       String_Type_Error
                                    );
                                 end if;
                                 State := Start_Length;
                                 exit;
                              when 0 =>
                                 Status.Mode := End_Of_Content;
                              when others =>
                                 Not_A_String (ASN1_Tag (Status.Length));
                           end case;
                     end case;
                  when End_Of_Content =>
                     if Data (Pointer) /= 0 then
                        Raise_Exception
                        (  Data_Error'Identity,
                           String_Invalid_EOC
                        );
                     end if;
                     Pointer := Pointer + 1;
                     State   := 0;
                     return;
               end case;
               if Pointer > Data'Last then
                  State := To_State (Status);
                  return;
               end if;
            end loop;
         end if;
      end loop;
   end Feed;

   procedure Get
             (  Data    : Stream_Element_Array;
                Pointer : in out Stream_Element_Offset;
                Value   : out String
             )  is
      Index : Stream_Element_Offset := Pointer;
   begin
      for Octet in Value'Range loop
         if Index > Data'Last then
            Raise_Exception (End_Error'Identity, Non_Terminated);
         end if;
         Value (Octet) := Character'Val (Data (Index));
         Index := Index + 1;
      end loop;
      Pointer := Index;
   end Get;

   function Get_Available
            (  Item  : Embedded_String_Data
            )  return Natural is
   begin
      return Item.Parent.Size - Item.Parent.Length;
   end Get_Available;

   function Get_Available
            (  Item  : External_String_Data
            )  return Natural is
   begin
      if Item.Buffer = null then
         return 0;
      else
         return Item.Buffer.Size - Item.Buffer.Length;
      end if;
   end Get_Available;

   function Get_ASN1_Type
            (  Item : Public_String_Data_Item
            )  return ASN1_Type is
   begin
      return UTF8_String_Tag;
   end Get_ASN1_Type;

   function Get_Length
            (  Item : Public_String_Data_Item
            )  return Natural is
   begin
      return Item.Length;
   end Get_Length;

   function Get_Value (Item : Public_String_Data_Item) return String is
   begin
      return Item.Value (1..Item.Length);
   end Get_Value;

   procedure Put
             (  Data    : in out Stream_Element_Array;
                Pointer : in out Stream_Element_Offset;
                Value   : String
             )  is
   begin
      if (  Pointer < Data'First
         or else
            (  Pointer > Data'Last
            and then
               Pointer - Data'Last /= 1
         )  )  then
         Raise_Exception (Layout_Error'Identity, Out_Of_Bounds);
      elsif Data'Last - Pointer < Value'Length - 1 then
         Raise_Exception (End_Error'Identity, No_Room);
      end if;
      for Index in Value'Range loop
         Data (Pointer) := Character'Pos (Value (Index));
         Pointer := Pointer + 1;
      end loop;
   end Put;

   procedure Put_BMP
             (  Data    : in out Stream_Element_Array;
                Pointer : in out Stream_Element_Offset;
                Value   : String
             )  is
   begin
      if (  Pointer < Data'First
         or else
            (  Pointer > Data'Last
            and then
               Pointer - Data'Last /= 1
         )  )  then
         Raise_Exception (Layout_Error'Identity, Out_Of_Bounds);
      end if;
      declare
         Count    : constant Stream_Element_Count :=
                             Stream_Element_Offset (Length (Value));
         Code     : UTF8_Code_Point;
         Index    : Stream_Element_Offset := Pointer;
         Position : Integer := Value'First;
      begin
         if Data'Last - Pointer < Count * 2 - 1 then
            Raise_Exception (End_Error'Identity, No_Room);
         end if;
         for Item in 1..Count loop
            Get (Value, Position, Code);
            if Code >= 2**16 then
               Raise_Exception (Constraint_Error'Identity, Unsupported);
            end if;
            Data (Index + 1) := Stream_Element (Code and 16#FF#);
            Code := Code / 256;
            Data (Index) := Stream_Element (Code and 16#FF#);
            Index := Index + 2;
         end loop;
         Pointer := Index;
      end;
   end Put_BMP;

   procedure Put_ITU_T61
             (  Data    : in out Stream_Element_Array;
                Pointer : in out Stream_Element_Offset;
                Value   : String
             )  is
      use Strings_Edit.UTF8.ITU_T61;
   begin
      if (  Pointer < Data'First
         or else
            (  Pointer > Data'Last
            and then
               Pointer - Data'Last /= 1
         )  )  then
         Raise_Exception (Layout_Error'Identity, Out_Of_Bounds);
      end if;
      declare
         Count    : constant Stream_Element_Count :=
                             Stream_Element_Offset (Length (Value));
         Code     : UTF8_Code_Point;
         Index    : Stream_Element_Offset := Pointer;
         Position : Integer := Value'First;
      begin
         if Data'Last - Pointer < Count - 1 then
            Raise_Exception (End_Error'Identity, No_Room);
         end if;
         for Item in 1..Count loop
            Get (Value, Position, Code);
            Data (Index) := Character'Pos (To_ITU_T61 (Code));
            Index := Index + 1;
         end loop;
         Pointer := Index;
      end;
   end Put_ITU_T61;

   procedure Put_Latin1
             (  Data    : in out Stream_Element_Array;
                Pointer : in out Stream_Element_Offset;
                Value   : String
             )  is
   begin
      if (  Pointer < Data'First
         or else
            (  Pointer > Data'Last
            and then
               Pointer - Data'Last /= 1
         )  )  then
         Raise_Exception (Layout_Error'Identity, Out_Of_Bounds);
      end if;
      declare
         Count    : constant Stream_Element_Count :=
                             Stream_Element_Offset (Length (Value));
         Code     : UTF8_Code_Point;
         Position : Integer := Value'First;
         Index    : Stream_Element_Offset := Pointer;
      begin
         if Data'Last - Pointer < Count - 1 then
            Raise_Exception (End_Error'Identity, No_Room);
         end if;
         for Item in 1..Count loop
            Get (Value, Position, Code);
            if Code >= 2**8 then
               Raise_Exception (Constraint_Error'Identity, Unsupported);
            end if;
            Data (Index) := Stream_Element (Code);
            Index := Index + 1;
         end loop;
         Pointer := Index;
      end;
   end Put_Latin1;

   procedure Put_Universal
             (  Data    : in out Stream_Element_Array;
                Pointer : in out Stream_Element_Offset;
                Value   : String
             )  is
   begin
      if (  Pointer < Data'First
         or else
            (  Pointer > Data'Last
            and then
               Pointer - Data'Last /= 1
         )  )  then
         Raise_Exception (Layout_Error'Identity, Out_Of_Bounds);
      end if;
      declare
         Count    : constant Stream_Element_Count :=
                             Stream_Element_Offset (Length (Value));
         Code     : UTF8_Code_Point;
         Position : Integer := Value'First;
         Index    : Stream_Element_Offset := Pointer;
      begin
         if Data'Last - Pointer < Count * 4 - 1 then
            Raise_Exception (End_Error'Identity, No_Room);
         end if;
         for Item in 1..Count loop
            Get (Value, Position, Code);
            Data (Index + 3) := Stream_Element (Code and 16#FF#);
            Code := Code / 256;
            Data (Index + 2) := Stream_Element (Code and 16#FF#);
            Code := Code / 256;
            Data (Index + 1) := Stream_Element (Code and 16#FF#);
            Code := Code / 256;
            Data (Index) := Stream_Element (Code and 16#FF#);
            Index := Index + 4;
         end loop;
         Pointer := Index;
      end;
   end Put_Universal;

   procedure Reset (Item : in out Embedded_String_Data) is
   begin
      Item.Parent.Length := 0;
   end Reset;

   procedure Reset (Item : in out External_String_Data) is
   begin
      if Item.Buffer = null then
         Item.Start := 1;
      else
         Item.Start  := Item.Buffer.Length + 1;
      end if;
      Item.Length := 0;
   end Reset;

   function Self
            (  Item : External_String_Buffer'Class
            )  return External_String_Buffer_Ptr is
      package From_Address is
         new System.Address_To_Access_Conversions
             (  External_String_Buffer'Class
             );
      use From_Address;
   begin
      return To_Pointer (Item'Address).all'Unchecked_Access;
   end Self;

   procedure Set_Value
             (  Item  : in out Public_String_Data_Item;
                Value : String
             )  is
   begin
      if Value'Length > Item.Size then
         Raise_Exception (Constraint_Error'Identity, Too_Large);
      else
         Item.Value (1..Value'Length) := Value;
         Item.Length := Value'Length;
      end if;
   end Set_Value;

   procedure Store
             (  Item    : in out Embedded_String_Data;
                Element : Stream_Element
             )  is
      Length : Natural renames Item.Parent.Length;
      Size   : Natural renames Item.Parent.Size;
   begin
      if Length + 1 > Size then
         Raise_Exception (Constraint_Error'Identity, Too_Large);
      end if;
      Length := Length + 1;
      Item.Parent.Value (Length) := Character'Val (Element);
   end Store;

   procedure Store
             (  Item     : in out Embedded_String_Data;
                Sequence : String
             )  is
      Length : Natural renames Item.Parent.Length;
      Size   : Natural renames Item.Parent.Size;
      Value  : String  renames Item.Parent.Value;
   begin
      if Length + Sequence'Length > Size then
         Raise_Exception (Constraint_Error'Identity, Too_Large);
      end if;
      Value (Length + 1..Length + Sequence'Length) := Sequence;
      Length := Length + Sequence'Length;
   end Store;

   procedure Store
             (  Item    : in out External_String_Data;
                Element : Stream_Element
             )  is
      Length : Natural renames Item.Buffer.Length;
      Size   : Natural renames Item.Buffer.Size;
   begin
      if Length + 1 > Size then
         Raise_Exception (Constraint_Error'Identity, Too_Large);
      end if;
      Length      := Length + 1;
      Item.Length := Item.Length + 1;
      Item.Buffer.Buffer (Length) := Character'Val (Element);
   end Store;

   procedure Store
             (  Item     : in out External_String_Data;
                Sequence : String
             )  is
      Length : Natural renames Item.Buffer.Length;
      Size   : Natural renames Item.Buffer.Size;
      Value  : String  renames Item.Buffer.Buffer;
   begin
      if Length + Sequence'Length > Size then
         Raise_Exception (Constraint_Error'Identity, Too_Large);
      end if;
      Value (Length + 1..Length + Sequence'Length) := Sequence;
      Length      := Length      + Sequence'Length;
      Item.Length := Item.Length + Sequence'Length;
   end Store;

   procedure Uninitialized (Item : ASN1_Data_Item'Class) is
   begin
      Raise_Exception
      (  Use_Error'Identity,
         (  "ASN.1 string "
         &  Expanded_Name (Item'Tag)
         &  " was not properly initialized"
      )  );
   end Uninitialized;

end GNAT.Sockets.Connection_State_Machine.ASN1.Strings;
