(********************************************************************)
(*                                                                  *)
(*  arc4.s7i      Support for ARC4 (Alleged RC4) stream cipher.     *)
(*  Copyright (C) 2013  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";


(**
 *  [[cipher|cipherState]] implementation type describing the state of an ARC4 cipher.
 *  The data is encrypted / decrypted with the ARC4 (Alleged RC4)
 *  stream cipher.
 *)
const type: arc4State is sub noCipherState struct
    var array char: s is [0 .. 255] times ' ';
    var integer: i is 0;
    var integer: j is 0;
  end struct;


type_implements_interface(arc4State, cipherState);


(**
 *  Block size used by the ARC4 (Alleged RC4) stream cipher.
 *  @return the block size used by the ARC4 cipher.
 *)
const func integer: blockSize (RC4) is 0;


(**
 *  Set the key for the ARC4 (Alleged RC4) stream cipher.
 *  @param arc4Key The key to be used for RC4.
 *  @return the RC4 (ARC4) cipher state.
 *)
const func arc4State: setArc4Key (in string: arc4Key) is func
  result
    var arc4State: state is arc4State.value;
  local
    var integer: i is 0;
    var integer: j is 0;
    var char: temp is ' ';
    var char: ch is ' ';
  begin
    for i range 0 to 255 do
      state.s[i] := chr(i);
    end for;
    for i range 0 to 255 do
      ch := arc4Key[succ(i mod length(arc4Key))];
      if ord(ch) > 255 then
        raise RANGE_ERROR;
      end if;
      j := (j + ord(state.s[i]) + ord(ch)) mod 256;
      temp := state.s[i];
      state.s[i] := state.s[j];
      state.s[j] := temp;
    end for;
  end func;


(**
 *  Set key and initialization vector for the ARC4 (Alleged RC4) stream cipher.
 *  @param cipherKey The key to be used for RC4.
 *  @param initializationVector Unused for RC4.
 *  @return the initial ''cipherState'' of a RC4 (ARC4) cipher.
 *)
const func cipherState: setCipherKey (RC4, in string: cipherKey,
    in string: initializationVector) is
  return toInterface(setArc4Key(cipherKey));


const func char: getArc4PseudoRandomByte (inout arc4State: state) is func
  result
    var char: keystreamChar is ' ';
  local
    var char: temp is ' ';
  begin
    state.i := succ(state.i) mod 256;
    state.j := (state.j + ord(state.s[state.i])) mod 256;
    temp := state.s[state.i];
    state.s[state.i] := state.s[state.j];
    state.s[state.j] := temp;
    keystreamChar := state.s[(ord(state.s[state.i]) + ord(state.s[state.j])) mod 256];
  end func;


(**
 *  Encode a string with the ARC4 (Alleged RC4) stream cipher.
 *  @return the encoded string.
 *)
const func string: encode (inout arc4State: state, in string: plaintext) is func
  result
    var string: encoded is "";
  local
    var char: ch is ' ';
    var char: keystreamChar is ' ';
  begin
    for ch range plaintext do
      keystreamChar := getArc4PseudoRandomByte(state);
      encoded &:= chr(ord(bin32(ord(ch)) >< bin32(ord(keystreamChar))));
    end for;
  end func;


(**
 *  Decode a string with the ARC4 (Alleged RC4) stream cipher.
 *  @return the decoded string.
 *)
const func string: decode (inout arc4State: state, in string: encoded) is func
  result
    var string: plaintext is "";
  local
    var char: ch is ' ';
    var char: keystreamChar is ' ';
  begin
    for ch range encoded do
      keystreamChar := getArc4PseudoRandomByte(state);
      plaintext &:= chr(ord(bin32(ord(ch)) >< bin32(ord(keystreamChar))));
    end for;
  end func;