-- -- -- package Copyright (c) Dmitry A. Kazakov -- -- GNAT.Sockets.Connection_State_Machine. Luebeck -- -- ASN1.Choices Summer, 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 Ada.Unchecked_Conversion; with System.Address_To_Access_Conversions; package body GNAT.Sockets.Connection_State_Machine.ASN1.Choices is Invalid_EOC : constant String := "Invalid indefinite ASN.1 " & "choice termination"; function Always_Constructed ( Item : Choice_Data_Item ) return Boolean is begin return True; end Always_Constructed; procedure Check (Item : Data_Item'Class) is begin if Item not in ASN1_Data_Item'Class then Raise_Exception ( Use_Error'Identity, Wrong_Choice & Expanded_Name (Item'Tag) ); end if; end Check; procedure Uninitialized (Item : Abstract_ASN1_Data_Item'Class) is begin Raise_Exception ( Use_Error'Identity, ( "ASN.1 Set " & Expanded_Name (Item'Tag) & " was not properly initialized" ) ); end Uninitialized; procedure Enable_Unsolicited ( Item : in out Choice_Data_Item; Enable : Boolean ) is begin if not Item.Initialized then Uninitialized (Item); end if; Item.Unsolicited := Enable; end Enable_Unsolicited; procedure Encode ( Item : Choice_Data_Item; Data : in out Stream_Element_Array; Pointer : in out Stream_Element_Offset ) is Tag : Tag_Type; 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 < 0 then Raise_Exception (End_Error'Identity, No_Room); elsif not Item.Initialized then Uninitialized (Item); elsif Item.Current.Position = 0 then Raise_Exception (Use_Error'Identity, Nothing_Selected); end if; Tag := Item.List (Item.Current.Position); declare This : Abstract_ASN1_Data_Item'Class renames Abstract_ASN1_Data_Item'Class ( Get (Item.Map, Tag).Item.all ); begin Put (Data, Pointer, Tag, Always_Constructed (This)); Definite_Encode (This, Data, Pointer); end; end Encode; Get_Element : constant := 1; Get_Tag_Value : constant := 2; Select_By_Tag : constant := 3; Skip_Until_Zero : constant := 4; Skip_Second_Zero : constant := 5; Skip_Definite : constant := 6; -- -- First_Zero : constant := 7; -- -- Second_Zero : constant := 8; -- procedure End_Of_Subsequence -- ( Item : in out Choice_Data_Item; -- Data : Stream_Element_Array; -- Pointer : Stream_Element_Offset; -- Client : in out State_Machine'Class; -- State : in out Stream_Element_Offset -- ) is -- begin -- if Item.Length >= 0 then -- if Item.Length = Get_Length (Item.Current) then -- State := 0; -- else -- Raise_Exception (Data_Error'Identity, Invalid_Length); -- end if; -- else -- State := First_Zero; -- end if; -- end End_Of_Subsequence; procedure Enumerate ( Stream : access Root_Stream_Type'Class; Item : Choice_Data_Item ) is begin Check_Initialization_Stream (Item, Stream.all); declare Count : Data_Item_Offset := 0; This : Initialization_Stream'Class renames Initialization_Stream'Class (Stream.all); begin if not Item.Initialized then declare Counter : aliased Initialization_Stream; Object : Choice_Data_Item'Class renames Self (Item).all; begin if This.Parent = null then Counter.Parent := This.Shared'Access; else Counter.Parent := This.Parent; end if; Object.Initialized := True; Choice_Data_Item'Class'Write (Counter'Access, Object); if Counter.Count <= 1 then Raise_Exception ( Use_Error'Identity, ( "ASN.1 set " & Expanded_Name (Object'Tag) & " is empty (contains no items)" ) ); end if; Object.List := new Tag_Type_Array ( 1 .. Natural (Counter.Count - 1) ); for Index in 2..Counter.Count loop declare Position : constant Positive := Positive (Index - 1); begin Check (Counter.Data.Vector (Index).all); Object.List (Position) := ( Class => Context_Specific_Tag, Value => ASN1_Type (Position - 1), Optional => False ); Add ( Object.Map, Object.List (Position), ( Position => Position, Item => Counter.Data.Vector (Index) ) ); end; end loop; Count := Data_Item_Offset (Get_Size (Item)) - 1; Initialized (Object); end; end if; Add (This, Item, Count); end; end Enumerate; procedure Feed ( Item : in out Choice_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 if State = 0 then Item.Current.Position := 0; State := Get_Element; end if; while Pointer <= Data'Last loop case State is when Get_Element => -- if Item.Length = 0 then -- Raise_Exception -- ( Data_Error'Identity, -- Invalid_Length -- ); -- end if; declare Tag : Tag_Type renames Item.Current.Tag; begin Tag.Value := ASN1_Type (Data (Pointer) and 16#1F#); case ASN1_Tag (Data (Pointer) and 2#1100_0000#) is when Application_Class => Tag.Class := Application_Tag; when Context_Specific_Class => Tag.Class := Context_Specific_Tag; when Private_Class => Tag.Class := Private_Tag; when others => Tag.Class := Universal_Tag; end case; if Tag.Value < 31 then State := Select_By_Tag; else State := Get_Tag_Value; end if; end; Pointer := Pointer + 1; -- if Item.Length > 0 then -- Item.Length := Item.Length - 1; -- end if; when Get_Tag_Value => while Pointer <= Data'Last loop -- if Item.Length = 0 then -- Raise_Exception -- ( Data_Error'Identity, -- Invalid_Length -- ); -- end if; declare This : constant ASN1_Type := ASN1_Type (Data (Pointer)); Value : ASN1_Type renames Item.Current.Tag.Value; begin Pointer := Pointer + 1; -- if Item.Length > 0 then -- Item.Length := Item.Length - 1; -- end if; if This > 127 then Value := Value * 128 + (This - 128); else Value := Value * 128 + This; State := Select_By_Tag; exit; end if; exception when Constraint_Error => Raise_Exception ( Data_Error'Identity, Tag_Too_Large ); end; end loop; when Select_By_Tag => declare Offset : constant Integer := Find (Item.Map, Item.Current.Tag); begin if Offset > 0 then declare Element : constant Element_Descriptor := Get (Item.Map, Offset); begin Item.Current.Position := Element.Position; -- State := First_Zero; State := 0; Call ( Item => Item.Current, Callee => Element.Item.all, Tag => Item.Current.Tag, Pointer => Pointer, Client => Client, State => State ); return; end; elsif Item.Unsolicited then State := Start_Length; else Raise_Exception ( Data_Error'Identity, ( "ASN.1 unexpected choice tag " & Image (Item.Current.Tag) ) ); end if; end; when Skip_Until_Zero => -- Skip indefinite length object while Data (Pointer) /= 0 loop Pointer := Pointer + 1; if Pointer > Data'Last then return; end if; end loop; State := Skip_Second_Zero; when Skip_Second_Zero => if Data (Pointer) = 0 then State := 0; return; else State := Skip_Until_Zero; end if; when Stream_Element_Offset'First..-1 => -- Skipped length while not Is_Length_Ready (State) loop if Pointer > Data'Last then return; end if; Embedded_Feed (Data, Pointer, State); end loop; if Is_Indefinite (State) then State := Skip_Until_Zero; else State := Skip_Definite + Get_Length (State); if State = Skip_Definite then State := 0; return; end if; end if; when others => -- Skip definite length object loop Pointer := Pointer + 1; State := State - 1; if State <= Skip_Definite then State := 0; return; elsif Pointer > Data'Last then return; end if; end loop; -- when First_Zero => -- if Data (Pointer) /= 0 then -- Raise_Exception (Data_Error'Identity, Invalid_EOC); -- end if; -- State := Second_Zero; -- Pointer := Pointer + 1; -- when others => -- if Data (Pointer) /= 0 then -- Raise_Exception (Data_Error'Identity, Invalid_EOC); -- end if; -- State := 0; -- Pointer := Pointer + 1; -- return; end case; end loop; end Feed; -- procedure Feed -- ( Item : in out Choice_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 -- Skip_Until_Zero : constant := 1; -- Skip_Definite : constant := 2; -- begin -- if Item.Position = 0 then -- while Pointer <= Data'Last loop -- ada.Text_IO.Put_Line(" feed ***IGNORED*** choice " -- &" pointer="&stream_element_offset'image(pointer) -- &" data=%"&image(integer(data(pointer)),base=>16) -- &" state="&stream_element_offset'image(State) -- &" length="&stream_element_offset'image(item.Length) -- &" "&Image(data(data'first..pointer-1))&"|"&Image(data(pointer..data'last)) -- ); -- case State is -- when 0 => -- Get ignored item length -- if not Item.Unsolicited then -- Raise_Exception -- ( Data_Error'Identity, -- ( "ASN.1 unexpected choice tag " -- & Image (Item.Tag) -- ) ); -- end if; -- if Item.Length < 0 then -- State := Skip_Until_Zero; -- elsif Item.Length = 0 then -- Zero length -- return; -- else -- State := Skip_Definite; -- end if; -- when Skip_Definite => -- Skip definite length object -- loop -- Pointer := Pointer + 1; -- Item.Length := Item.Length - 1; -- if Item.Length <= 0 then -- State := 0; -- return; -- elsif Pointer > Data'Last then -- return; -- end if; -- end loop; -- when others => -- Skip indefinite length object -- loop -- if Data (Pointer) = 0 then -- The first zero, leave -- State := 0; -- it be -- return; -- end if; -- Pointer := Pointer + 1; -- if Pointer > Data'Last then -- return; -- end if; -- end loop; -- end case; -- end loop; -- else -- ada.Text_IO.Put_Line(" feed choice " -- &" pointer="&stream_element_offset'image(pointer) -- &" data=%"&image(integer(data(pointer)),base=>16) -- &" state="&stream_element_offset'image(State) -- &" length="&stream_element_offset'image(item.Length) -- &" "&Image(data(data'first..pointer-1))&"|"&Image(data(pointer..data'last)) -- ); -- Feed -- ( Get (Item.Map, Item.Position).Item.all, -- Data, -- Pointer, -- Client, -- State -- ); -- end if; -- end Feed; procedure Finalize ( Item : in out Choice_Data_Item ) is procedure Free is new Ada.Unchecked_Deallocation ( Tag_Type_Array, Tag_Type_Array_Ptr ); begin Free (Item.List); end Finalize; function Get_Children ( Item : Choice_Data_Item ) return Data_Item_Ptr_Array is begin if not Item.Initialized then Uninitialized (Item); end if; declare Result : Data_Item_Ptr_Array (1..Item.List'Length); begin for Index in Result'Range loop Result (Index) := Get (Item.Map, Integer (Index)).Item; end loop; return Result; end; end Get_Children; function Get_Length ( Item : Choice_Data_Item ) return Natural is begin if not Item.Initialized then Uninitialized (Item); end if; return Item.List'Length; end Get_Length; function Get_Selected ( Item : Choice_Data_Item ) return Natural is begin if not Item.Initialized then Uninitialized (Item); end if; return Item.Current.Position; end Get_Selected; function Get_Selected ( Item : Choice_Data_Item ) return Abstract_ASN1_Data_Item_Ptr is begin if not Item.Initialized then Uninitialized (Item); end if; if Item.Current.Position = 0 then return null; else return Abstract_ASN1_Data_Item'Class ( Get ( Item.Map, Item.List (Item.Current.Position) ) .Item.all ) 'Unchecked_Access; end if; end Get_Selected; function Get_Size (Item : Choice_Data_Item) return Natural is begin if not Item.Initialized then Uninitialized (Item); end if; declare Result : Natural := 1; begin for Index in 1..Get_Size (Item.Map) loop Result := Result + Get_Size (Get (Item.Map, Index).Item.all); end loop; return Result; end; end Get_Size; function Get_Tag ( Item : Choice_Data_Item; Index : Positive ) return Tag_Type is begin if not Item.Initialized then Uninitialized (Item); end if; if Index > Item.List'Last then Raise_Exception (Constraint_Error'Identity, Wrong_Index); else return Item.List (Index); end if; end Get_Tag; procedure Initialized (Item : in out Choice_Data_Item) is begin null; end Initialized; function Is_Implicit (Item : Choice_Data_Item) return Boolean is begin return True; end Is_Implicit; function Is_Unsolicited_Enabled ( Item : Choice_Data_Item ) return Boolean is begin if not Item.Initialized then Uninitialized (Item); end if; return Item.Unsolicited; end Is_Unsolicited_Enabled; function Resolve_Selected ( Item : Abstract_ASN1_Data_Item'Class ) return Abstract_ASN1_Data_Item_Ptr is Current : Abstract_ASN1_Data_Item_Ptr := Abstract_ASN1_Data_Item'Class ( Self (Item).all ) 'Unchecked_Access; begin loop if Current.all in ASN1_Data_Item'Class then return Current; elsif Current.all not in Choice_Data_Item'Class then Raise_Exception ( Data_Error'Identity, Wrong_Choice & Expanded_Name (Current.all'Tag) ); end if; Current := Get_Selected (Choice_Data_Item'Class (Current.all)); if Current = null then Raise_Exception (Data_Error'Identity, Nothing_Selected); end if; end loop; end Resolve_Selected; function Self ( Item : Choice_Data_Item'Class ) return Choice_Data_Item_Ptr is package From_Set_Address is new System.Address_To_Access_Conversions ( Choice_Data_Item'Class ); use From_Set_Address; begin return To_Pointer (Item'Address).all'Unchecked_Access; end Self; procedure Set_Implicit_Tag ( Item : in out Choice_Data_Item; Tag : Tag_Type; Length : Stream_Element_Offset ) is begin if Length = 0 then Raise_Exception (Data_Error'Identity, Invalid_Length); end if; end Set_Implicit_Tag; procedure Set_Selected ( Item : in out Choice_Data_Item; Index : Positive ) is begin if not Item.Initialized then Uninitialized (Item); elsif Index > Item.List'Last then Raise_Exception (Constraint_Error'Identity, Wrong_Index); else Item.Current.Position := Index; end if; end Set_Selected; procedure Set_Tag ( Item : in out Choice_Data_Item; Index : Positive; Tag : Tag_Type ) is begin if not Item.Initialized then Uninitialized (Item); elsif Index > Item.List'Last then Raise_Exception (Constraint_Error'Identity, Wrong_Index); elsif not Tag.Optional then Raise_Exception ( Mode_Error'Identity, "ASN.1 set element is not otpional" ); end if; declare Element : Element_Descriptor; Offset : Integer := Find (Item.Map, Tag); begin if Offset > 0 then Element := Get (Item.Map, Offset); if Element.Position /= Index then Raise_Exception ( Constraint_Error'Identity, ( "ASN.1 set already contains element tagged by " & Image (Tag) ) ); end if; else Offset := Find (Item.Map, Item.List (Index)); Element := Get (Item.Map, Offset); if Tag.Class = Universal_Tag then declare Object : Abstract_ASN1_Data_Item'Class renames Abstract_ASN1_Data_Item'Class ( Element.Item.all ); begin if not Is_Implicit (Object) then Raise_Exception ( Mode_Error'Identity, "Universally tagged object is not implicit" ); elsif Object not in ASN1_Data_Item'Class then Raise_Exception ( Mode_Error'Identity, "Universally tagged object has no definite type" ); elsif ( Tag.Value /= Get_ASN1_Type (ASN1_Data_Item'Class (Object)) ) then Raise_Exception ( Mode_Error'Identity, ( "The universal tag value differs " & "from the object type" ) ); end if; end; end if; Remove (Item.Map, Offset); Add (Item.Map, Tag, Element); Item.List (Index) := Tag; end if; end; end Set_Tag; procedure Set_Untagged (Item : in out Choice_Data_Item) is begin null; end Set_Untagged; end GNAT.Sockets.Connection_State_Machine.ASN1.Choices;