--  Copyright (C) 2015  Daniel Otte <bg@nerilex.org>
--
--  This program 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 3 of the License, or
--  (at your option) any later version.
--
--  This program 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, see <http://www.gnu.org/licenses/>.

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Float_Text_IO; use Ada.Float_Text_IO;
--  with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with Ada.Strings; use Ada.Strings;
with Ada.Strings.Fixed; use Ada.Strings.Fixed;
with Crypto_Types; use Crypto_Types;
with System; use System;
use Crypto_Types.Crypto_Types_u8;


--  ********************************************************************************
--  *Project NESSIE - New European Schemes for Signature, Integrity, and Encryption*
--  ********************************************************************************
--
--  Primitive Name: Des
--  ===================
--  Key size: 64 bits
--  Block size: 64 bits
--
--  Test vectors -- set 1
--  =====================
--
--  Set 1, vector#  0:
--  key=8000000000000000
--  Plain=0000000000000000
--  cipher=95A8D72813DAA94D
--  decrypted=0000000000000000
--  Iterated 100 times=F749E1F8DEFAF605
--  Iterated 1000 times=F396DD0B33D04244
--
--  Set 1, vector#  1:
--  key=4000000000000000
--  plain=0000000000000000
--  cipher=0EEC1487DD8C26D5
--  decrypted=0000000000000000
--  Iterated 100 times=E5BEE86B600F3B48
--  Iterated 1000 times=1D5931D700EF4E15


package body Nessie_BC_Test_Generator is

   Total_Number_Of_Encrypts : constant Natural :=
     (Key_Size_Bits + Block_Size_Bits + 256 + 2) * (1 + 1000 + 1);

   Total_Number_Of_Decrypts : constant Natural :=
     (Key_Size_Bits + Block_Size_Bits + 256 + 2) * (1 + 1000 + 1);

   Total_Number_Of_Primitive_Runs : constant Natural :=
     Total_Number_Of_Encrypts + Total_Number_Of_Decrypts;

   Primitive_Runs_Per_Encrypt_Test : constant := 2002;
   Primitive_Runs_Per_Decrypt_Test : constant := 2;

   Step : Natural;

   Kasumi_Test_Key : constant u8_Array(1 .. 16) :=
     (
      16#2B#, 16#D6#, 16#45#, 16#9F#, 16#82#, 16#C5#, 16#B3#, 16#00#,
      16#95#, 16#2C#, 16#49#, 16#10#, 16#48#, 16#81#, 16#FF#, 16#48#
     );

   Kasumi_Test_Plain : constant u8_Array(1 .. 8) :=
     (
      16#EA#, 16#02#, 16#47#, 16#14#, 16#AD#, 16#5C#, 16#4D#, 16#84#
     );

   procedure Print_Header is
   begin
      Put_Line("********************************************************************************");
      Put_Line("* SteelCrypt - NESSIE TestVectors                                              *");
      Put_Line("********************************************************************************");
      New_Line;
      Put_Line("Primitive Name: " & Name);
      Put_Line((16 + Name'Length) * '=');
      Put_Line("Key size:" & Integer'Image(Key_Size_Bits) & " bits");
      Put_Line("Block size:" & Integer'Image(Block_Size_Bits) & " bits");
      New_Line;
   end Print_Header;

   procedure Print_Set_Header(Set_No : in Positive) is
   begin
      Put_Line("Test vectors -- set" & Integer'Image(Set_No));
      Put_Line("=====================");
      New_Line;
   end Print_Set_Header;

   procedure Print_Vector_Header(Set_No : in Positive; Vector_No : in Natural) is
      Vector_No_Str : constant String := Trim(Source => Integer'Image(Vector_No), Side => Both);
   begin
      Put("Set" & Integer'Image(Set_No) &", vector#");
      Put((3 - Vector_No_Str'Length) * ' ');
      Put_Line(Vector_No_Str & ":");
   end Print_Vector_Header;


   procedure Print_Item(Tag : in String; Block : in u8_Array) is
      Split : constant Boolean := Block'Length > 24;
      j : Integer := Block'First;
   begin
      if Tag'Length < 30 then
         Put((30 - Tag'Length) * ' ');
      end if;
      if Split then
         Put_Line(Tag & "=" & To_Hex(A => Block(j .. j + 15), Upper_Case => True));
         j := j + 16;
         while j + 16 < Block'Last loop
            Put_Line(31 * ' ' & To_Hex(A => Block(j .. j + 15), Upper_Case => True));
            j := j + 16;
         end loop;
         if j <= Block'Last then
            Put_Line(31 * ' ' & To_Hex(A => Block(j .. Block'Last), Upper_Case => True));
         end if;
      else
         Put_Line(Tag & "=" & To_Hex(A => Block, Upper_Case => True));
      end if;

   end Print_Item;


   procedure Run_Encrypt_Test(Key : in Key_T; Block : in Block_T) is
      Temp : Block_T := Block;
      Context : Context_T;
   begin
      Print_Item("key", Key);
      Print_Item("plain", Temp);
      Initialize(Context, Key);
      Encrypt(Context, Temp);
      Print_Item("cipher", Temp);
      Decrypt(Context, Temp);
      Print_Item("decrypted", Temp);
      if Temp /= Block then
         Put_Line("!!! decrypted /= plain");
      end if;
      for i in 1 .. 100 loop
         Encrypt(Context, Temp);
      end loop;
      Print_Item("Iterated 100 times", Temp);
      for i in 101 .. 1000 loop
         Encrypt(Context, Temp);
      end loop;
      Print_Item("Iterated 1000 times", Temp);
      for i in 1 .. 1000 loop
         Decrypt(Context, Temp);
      end loop;
      if Temp /= Block then
         Put_Line("!!! decrypted /= plain");
      end if;
   end Run_Encrypt_Test;

   procedure Run_Decrypt_Test(Key : in Key_T; Block : in Block_T) is
      Temp : Block_T := Block;
      Context : Context_T;
   begin
      Print_Item("key", Key);
      Print_Item("cipher", Temp);
      Initialize(Context, Key);
      Decrypt(Context, Temp);
      Print_Item("plain", Temp);
      Encrypt(Context, Temp);
      Print_Item("encrypted", Temp);
      if Temp /= Block then
         Put_Line("!!! encrypted /= plain");
      end if;
   end Run_Decrypt_Test;

   procedure Initialize_Display is
   begin
      Step := 0;
      if Verbose then
         New_Line(File => Standard_Error);
      end if;
   end Initialize_Display;

   procedure Update_Display is
   begin
      if Verbose then
         --         Set_Col(File => Standard_Error, To => 1);
         Put(File => Standard_Error,
             Item => Character'Val(13) & " ==> " & Name & ": ");
         --           Put(File => Standard_Error, Item => Float'Image(100.0 * Float(Step) / Float(Total_Number_Of_Primitive_Runs)) & "%");
         Put(File => Standard_Error,
             Item => 100.0 * Float(Step) / Float(Total_Number_Of_Primitive_Runs),
             Fore => 3,
             Aft  => 2,
             Exp  => 0 );
         Put(File => Standard_Error,
             Item => '%' );
      end if;
   end Update_Display;


   procedure Run_Test(Key : in Key_T; Block : in Block_T; Encrypt_Test : in Boolean := True) is
   begin
      if Encrypt_Test then
         Run_Encrypt_Test(Key, Block);
         Step := Step + Primitive_Runs_Per_Encrypt_Test;
      else
         Run_Decrypt_Test(Key, Block);
         Step := Step + Primitive_Runs_Per_Decrypt_Test;
      end if;
      Update_Display;
   end Run_Test;

   procedure Set_1(Set_No : in Positive; Encrypt_Test : in Boolean := True) is
      Key : Key_T;
      Block : constant Block_T := (others => 0);
   begin
      Print_Set_Header(Set_No);
      for i in 0 .. Key_Size_Bits - 1 loop
         Print_Vector_Header(Set_No, i);
         Key := (others => 0);
         Bit_Set(A => Key, Bit_Address => i, Value => 1, Order => High_Order_First);
         Run_Test(Key, Block, Encrypt_Test);
         New_Line;
      end loop;
   end Set_1;

   procedure Set_2(Set_No : in Positive; Encrypt_Test : in Boolean := True) is
      Key : constant Key_T := (others => 0);
      Block : Block_T;
   begin
      Print_Set_Header(Set_No);
      for i in 0 .. Block_Size_Bits - 1 loop
         Print_Vector_Header(Set_No, i);
         Block := (others => 0);
         Bit_Set(A => Block, Bit_Address => i, Value => 1, Order => High_Order_First);
         Run_Test(Key, Block, Encrypt_Test);
         New_Line;
      end loop;
   end Set_2;

   procedure Set_3(Set_No : in Positive; Encrypt_Test : in Boolean := True) is
      Key : Key_T;
      Block : Block_T;
   begin
      Print_Set_Header(Set_No);
      for i in u8'Range loop
         Print_Vector_Header(Set_No, Natural(i));
         Block := (others => i);
         Key := (others => i);
         Run_Test(Key, Block, Encrypt_Test);
         New_Line;
      end loop;
   end Set_3;

   procedure Set_4(Set_No : in Positive; Encrypt_Test : in Boolean := True) is
      Key : Key_T;
      Block : Block_T;
   begin
      Print_Set_Header(Set_No);

      Print_Vector_Header(Set_No, 0);
      for i in 0 .. Key'Length - 1 loop
         Key(Key'First + i) :=  u8(i mod 256);
      end loop;
      for i in 0 .. Block'Length - 1 loop
         Block(Block'First + i) :=  u8((17 * i) mod 256);
      end loop;
      Run_Test(Key, Block, Encrypt_Test);
      New_Line;

      Print_Vector_Header(Set_No, 1);
      for i in 0 .. Key'Length - 1 loop
         Key(Key'First + i) :=  Kasumi_Test_Key(Kasumi_Test_Key'First + (i mod Kasumi_Test_Key'Length));
      end loop;
      for i in 0 .. Block'Length - 1 loop
         Block(Block'First + i) :=  Kasumi_Test_Plain(Kasumi_Test_Plain'First + (i mod Kasumi_Test_Plain'Length));
      end loop;
      Run_Test(Key, Block, Encrypt_Test);
      New_Line;
   end Set_4;

   procedure Run(FileName : String := "") is
      File : File_Type;
      Redirected : Boolean := False;
   begin
      Initialize_Display;
      if FileName /= "" and FileName /= "-" then
         Redirected := True;
         Create(File => File, Name => FileName, Mode => Out_File);
         Set_Output(File);
      end if;
      Print_Header;
      Set_1(1, True);
      Set_2(2, True);
      Set_3(3, True);
      Set_4(4, True);
      Set_1(5, False);
      Set_2(6, False);
      Set_3(7, False);
      Set_4(8, False);
      New_Line; New_Line;
      Put_Line("End of test vectors");
      Flush;
      if Redirected then
         Close(File);
         Set_Output(Standard_Output);
      end if;
   end Run;

   procedure Run_File is
   begin
      Run(Name & Default_Suffix);
   end Run_File;


end Nessie_BC_Test_Generator;
