context: using System;
  
  using Nequeo.Cryptography.Key.Crypto.Parameters;
  
  namespace Nequeo.Cryptography.Key.Crypto.Engines
  {
      /**
      * The specification for RC5 came from the <code>RC5 Encryption Algorithm</code>
      * publication in RSA CryptoBytes, Spring of 1995.
      * <em>http://www.rsasecurity.com/rsalabs/cryptobytes</em>.
      * <p>
      * This implementation has a word size of 32 bits.</p>
      */
      public class RC532Engine
  		: IBlockCipher
      {
          /*
          * the number of rounds to perform
          */
          private int _noRounds;
  
          /*
          * the expanded key array of size 2*(rounds + 1)
          */
          private int [] _S;
  
          /*
          * our "magic constants" for 32 32
          *
          * Pw = Odd((e-2) * 2^wordsize)
          * Qw = Odd((o-2) * 2^wordsize)
          *
          * where e is the base of natural logarithms (2.718281828...)
          * and o is the golden ratio (1.61803398...)
          */
          private static readonly int P32 = unchecked((int) 0xb7e15163);
          private static readonly int Q32 = unchecked((int) 0x9e3779b9);
  
          private bool forEncryption;
  
          /**
          * Create an instance of the RC5 encryption algorithm
          * and set some defaults
          */
          public RC532Engine()
          {
              _noRounds     = 12;         // the default
  //            _S            = null;
          }
  
          public string AlgorithmName
          {
              get { return "RC5-32"; }
          }
  
  		public bool IsPartialBlockOkay
  		{
  			get { return false; }
  		}
  
  		public int GetBlockSize()
          {
              return 2 * 4;
          }
  
  		/**
          * initialise a RC5-32 cipher.
          *
          * @param forEncryption whether or not we are for encryption.
          * @param parameters the parameters required to set up the cipher.
          * @exception ArgumentException if the parameters argument is
          * inappropriate.
          */
          public void Init(
              bool				forEncryption,
              ICipherParameters	parameters)
          {
              if (typeof(RC5Parameters).IsInstanceOfType(parameters))
              {
                  RC5Parameters p = (RC5Parameters)parameters;
  
                  _noRounds = p.Rounds;
  
                  SetKey(p.GetKey());
              }
              else if (typeof(KeyParameter).IsInstanceOfType(parameters))
              {
                  KeyParameter p = (KeyParameter)parameters;
  
                  SetKey(p.GetKey());
              }
              else
              {
                  throw new ArgumentException("invalid parameter passed to RC532 init - " + parameters.GetType().ToString());
              }
  
              this.forEncryption = forEncryption;
          }
  
          public int ProcessBlock(
              byte[]	input,
              int		inOff,
              byte[]	output,
              int		outOff)
          {
              return (forEncryption)
  				?	EncryptBlock(input, inOff, output, outOff)
  				:	DecryptBlock(input, inOff, output, outOff);
          }
  
  		public void Reset()
          {
          }
  
          /**
          * Re-key the cipher.
          *
          * @param  key  the key to be used
          */
          private void SetKey(
              byte[] key)
          {
              //
              // KEY EXPANSION:
              //
              // There are 3 phases to the key expansion.
              //
              // Phase 1:
              //   Copy the secret key K[0...b-1] into an array L[0..c-1] of
              //   c = ceil(b/u), where u = 32/8 in little-endian order.
              //   In other words, we fill up L using u consecutive key bytes
              //   of K. Any unfilled byte positions in L are zeroed. In the
              //   case that b = c = 0, set c = 1 and L[0] = 0.
              //
              int[]   L = new int[(key.Length + (4 - 1)) / 4];
  
              for (int i = 0; i != key.Length; i++)
              {
                  L[i / 4] += (key[i] & 0xff) << (8 * (i % 4));
              }
  
              //
              // Phase 2:
              //   Initialize S to a particular fixed pseudo-random bit pattern
              //   using an arithmetic progression modulo 2^wordsize determined
              //   by the magic numbers, Pw & Qw.
              //
              _S            = new int[2*(_noRounds + 1)];
  
              _S[0] = P32;
              for (int i=1; i < _S.Length; i++)
              {
                  _S[i] = (_S[i-1] + Q32);
              }
  
              //
              // Phase 3:
              //   Mix in the user's secret key in 3 passes over the arrays S & L.
              //   The max of the arrays sizes is used as the loop control
              //
              int iter;
  
              if (L.Length > _S.Length)
              {
                  iter = 3 * L.Length;
              }
              else
              {
                  iter = 3 * _S.Length;
              }
  
              int A = 0, B = 0;
              int ii = 0, jj = 0;
  
              for (int k = 0; k < iter; k++)
              {
                  A = _S[ii] = RotateLeft(_S[ii] + A + B, 3);
                  B =  L[jj] = RotateLeft( L[jj] + A + B, A+B);
                  ii = (ii+1) % _S.Length;
                  jj = (jj+1) %  L.Length;
              }
          }
  
          /**
          * Encrypt the given block starting at the given offset and place
          * the result in the provided buffer starting at the given offset.
          *
          * @param  in     in byte buffer containing data to encrypt
          * @param  inOff  offset into src buffer
          * @param  out     out buffer where encrypted data is written
          * @param  outOff  offset into out buffer
          */
          private int EncryptBlock(
              byte[]  input,
              int     inOff,
              byte[]  outBytes,
              int     outOff)
          {
              int A = BytesToWord(input, inOff) + _S[0];
              int B = BytesToWord(input, inOff + 4) + _S[1];
  
              for (int i = 1; i <= _noRounds; i++)
              {
                  A = RotateLeft(A ^ B, B) + _S[2*i];
                  B = RotateLeft(B ^ A, A) + _S[2*i+1];
              }
  
              WordToBytes(A, outBytes, outOff);
              WordToBytes(B, outBytes, outOff + 4);
  
              return 2 * 4;
          }
  
          private int DecryptBlock(
              byte[]  input,
              int     inOff,
              byte[]  outBytes,
              int     outOff)
          {
              int A = BytesToWord(input, inOff);
              int B = BytesToWord(input, inOff + 4);
  
              for (int i = _noRounds; i >= 1; i--)
              {
                  B = RotateRight(B - _S[2*i+1], A) ^ A;
  
gt:                 A = RotateRight(A - _S[2*i],   B) ^ B;