(********************************************************************)
(*                                                                  *)
(*  tdes.s7i      TDES (Triple DES) cipher support.                 *)
(*  Copyright (C) 2014, 2015, 2023  Thomas Mertes                   *)
(*                                                                  *)
(*  This file is part of the Seed7 Runtime Library.                 *)
(*                                                                  *)
(*  The Seed7 Runtime Library is free software; you can             *)
(*  redistribute it and/or modify it under the terms of the GNU     *)
(*  Lesser General Public License as published by the Free Software *)
(*  Foundation; either version 2.1 of the License, or (at your      *)
(*  option) any later version.                                      *)
(*                                                                  *)
(*  The Seed7 Runtime 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 Lesser General Public License for more    *)
(*  details.                                                        *)
(*                                                                  *)
(*  You should have received a copy of the GNU Lesser General       *)
(*  Public License along with this program; if not, write to the    *)
(*  Free Software Foundation, Inc., 51 Franklin Street,             *)
(*  Fifth Floor, Boston, MA  02110-1301, USA.                       *)
(*                                                                  *)
(********************************************************************)


include "bin32.s7i";
include "cipher.s7i";
include "des.s7i";


(**
 *  [[cipher|cipherState]] implementation type describing the state of a TDES cipher.
 *  The data is encrypted / decrypted with the TDES (Triple DES)
 *  block cipher.
 *)
const type: tdesState is sub noCipherState struct
    var desSubKeyType: encryptionSubKey1 is desSubKeyType.value;
    var desSubKeyType: encryptionSubKey2 is desSubKeyType.value;
    var desSubKeyType: encryptionSubKey3 is desSubKeyType.value;
    var desSubKeyType: decryptionSubKey1 is desSubKeyType.value;
    var desSubKeyType: decryptionSubKey2 is desSubKeyType.value;
    var desSubKeyType: decryptionSubKey3 is desSubKeyType.value;
    var string: cipherBlock is "";
  end struct;


type_implements_interface(tdesState, cipherState);


(**
 *  Block size used by the TDES (Triple DES) block cipher.
 *  @return the block size used by the TDES cipher.
 *)
const func integer: blockSize (TDES) is 8;


(**
 *  Set key and initialization vector for the TDES (Triple DES) block cipher.
 *  @param desKey The key to be used for TDES.
 *  @param initializationVector The initialisation vector (IV) for TDES.
 *  @return the TDES (Triple DES) cipher state.
 *)
const func tdesState: setTdesKey (in string: desKey, in string: initializationVector) is func
  result
    var tdesState: state is tdesState.value;
  begin
    state.encryptionSubKey1 := setDesKey(desKey[1 fixLen 8]);
    state.decryptionSubKey1 := reverseKeyScheduleOrder(state.encryptionSubKey1);
    state.encryptionSubKey2 := setDesKey(desKey[9 fixLen 8]);
    state.decryptionSubKey2 := reverseKeyScheduleOrder(state.encryptionSubKey2);
    state.encryptionSubKey3 := setDesKey(desKey[17 fixLen 8]);
    state.decryptionSubKey3 := reverseKeyScheduleOrder(state.encryptionSubKey3);
    state.cipherBlock := initializationVector;
  end func;


(**
 *  Set key and initialization vector for the TDES (Triple DES) block cipher.
 *  @param cipherKey The key to be used for TDES.
 *  @param initializationVector The initialisation vector (IV) for TDES.
 *  @return the initial ''cipherState'' of a TDES cipher.
 *)
const func cipherState: setCipherKey (TDES, in string: cipherKey,
    in string: initializationVector) is
  return toInterface(setTdesKey(cipherKey, initializationVector));


(**
 *  Encode a string with the TDES (Triple DES) block cipher.
 *  @return the encoded string.
 *)
const func string: encode (inout tdesState: state, in string: plaintext) is func
  result
    var string: encoded is "";
  local
    var integer: index is 0;
    var integer: subIndex is 0;
    var string: dataBlock is "";
    var string: cipherBlock is "";
  begin
    for index range 1 to length(plaintext) step blockSize(TDES) do
      dataBlock := "";
      for subIndex range 1 to blockSize(TDES) do
        dataBlock &:= chr(ord(bin32(plaintext[pred(index + subIndex)]) ><
                              bin32(state.cipherBlock[subIndex])));
      end for;
      cipherBlock := processDesBlock(state.encryptionSubKey3,
                     processDesBlock(state.decryptionSubKey2,
                     processDesBlock(state.encryptionSubKey1, dataBlock)));
      state.cipherBlock := cipherBlock;
      encoded &:= cipherBlock;
    end for;
  end func;


(**
 *  Decode a string with the TDES (Triple DES) block cipher.
 *  @return the decoded string.
 *)
const func string: decode (inout tdesState: state, in string: encoded) is func
  result
    var string: plaintext is "";
  local
    var integer: index is 0;
    var integer: subIndex is 0;
    var string: cipherBlock is "";
    var string: dataBlock is "";
    var string: plainBlock is "";
  begin
    for index range 1 to length(encoded) step blockSize(TDES) do
      cipherBlock := encoded[index fixLen blockSize(TDES)];
      dataBlock := processDesBlock(state.decryptionSubKey1,
                   processDesBlock(state.encryptionSubKey2,
                   processDesBlock(state.decryptionSubKey3, cipherBlock)));
      for subIndex range 1 to blockSize(TDES) do
        plaintext &:= chr(ord(bin32(dataBlock[subIndex]) ><
                              bin32(state.cipherBlock[subIndex])));
      end for;
      state.cipherBlock := cipherBlock;
    end for;
  end func;