/*
 * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 * 
 * Some of the code in this class is derived from ccRtp's SRTP implementation,
 * which has the following copyright notice: 
 *
  Copyright (C) 2004-2006 the Minisip Team

  This 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.

  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
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser 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
*/
package gnu.java.zrtp.jmf.transform.srtp;

import java.util.*;

import org.bouncycastle.crypto.engines.AESFastEngine;
import org.bouncycastle.crypto.params.KeyParameter;

/**
 * SRTPCipherF8 implements SRTP F8 Mode AES Encryption (AES-f8).
 * F8 Mode AES Encryption algorithm is defined in RFC3711, section 4.1.2.
 * 
 * Other than Null Cipher, RFC3711 defined two two encryption algorithms:
 * Counter Mode AES Encryption and F8 Mode AES encryption. Both encryption
 * algorithms are capable to encrypt / decrypt arbitrary length data, and the
 * size of packet data is not required to be a multiple of the AES block 
 * size (128bit). So, no padding is needed.
 * 
 * Please note: these two encryption algorithms are specially defined by SRTP.
 * They are not common AES encryption modes, so you will not be able to find a 
 * replacement implementation in common cryptographic libraries. 
 * 
 * As defined by RFC3711: F8 mode encryption is optional.
 *
 *                        mandatory to impl     optional      default
 * -------------------------------------------------------------------------
 *   encryption           AES-CM, NULL          AES-f8        AES-CM
 *   message integrity    HMAC-SHA1                -          HMAC-SHA1
 *   key derivation       (PRF) AES-CM             -          AES-CM 
 *
 * We use AESCipher to handle basic AES encryption / decryption.
 * 
 * @author Bing SU (nova.su@gmail.com)
 */
public class SRTPCipherF8
{
    /**
     * AES block size, just a short name.
     */
    private final static int BLKLEN = 16;

    /**
     * F8 mode encryption context, see RFC3711 section 4.1.2 for detailed
     * description.
     */
    class F8Context
    {
        public byte[] S;
        public byte[] ivAccent;
        long J;
    }

    /* (non-Javadoc)
     * @see net.java.sip.communicator.impl.media.transform.srtp.
     * SRTPCipher#process(byte[], int, int, byte[])
     */
    public static void process(AESFastEngine aesCipher, byte[] data, int off, int len,
            byte[] iv, byte[] key, byte[] salt, AESFastEngine f8Cipher) {
        F8Context f8ctx = new SRTPCipherF8().new F8Context();

        /*
         * Get memory for the derived IV (IV')
         */
        f8ctx.ivAccent = new byte[BLKLEN];

        /*
         * Get memory for the special key. This is the key to compute the
         * derived IV (IV').
         */
        byte[] saltMask = new byte[key.length];
        byte[] maskedKey = new byte[key.length];

        /*
         * First copy the salt into the mask field, then fill with 0x55 to get a
         * full key.
         */
        System.arraycopy(salt, 0, saltMask, 0, salt.length);
        for (int i = salt.length; i < saltMask.length; ++i) {
            saltMask[i] = 0x55;
        }

        /*
         * XOR the original key with the above created mask to get the special
         * key.
         */
        for (int i = 0; i < key.length; i++) {
            maskedKey[i] = (byte) (key[i] ^ saltMask[i]);
        }

        /*
         * Prepare the f8Cipher with the special key to compute IV'
         */
        KeyParameter encryptionKey = new KeyParameter(maskedKey);
        f8Cipher.init(true, encryptionKey);
        /*
         * Use the masked key to encrypt the original IV to produce IV'.
         */
        f8Cipher.processBlock(iv, 0, f8ctx.ivAccent, 0);
        saltMask = null;
        maskedKey = null;

        f8ctx.J = 0; // initialize the counter
        f8ctx.S = new byte[BLKLEN]; // get the key stream buffer

        Arrays.fill(f8ctx.S, (byte) 0);

        int inLen = len;

        while (inLen >= BLKLEN) {
            processBlock(aesCipher, f8ctx, data, off, data, off, BLKLEN);
            inLen -= BLKLEN;
            off += BLKLEN;
        }

        if (inLen > 0) {
            processBlock(aesCipher, f8ctx, data, off, data, off, inLen);
        }
    }
    
    /**
     * Encrypt / Decrypt a block using F8 Mode AES algorithm, read len bytes
     * data from in at inOff and write the output into out at outOff
     * 
     * @param f8ctx
     *            F8 encryption context
     * @param in
     *            byte array holding the data to be processed
     * @param inOff
     *            start offset of the data to be processed inside in array
     * @param out
     *            byte array that will hold the processed data
     * @param outOff
     *            start offset of output data in out
     * @param len
     *            length of the input data
     */
    private static void processBlock(AESFastEngine aesCipher, F8Context f8ctx,
            byte[] in, int inOff, byte[] out, int outOff, int len) {

        /*
         * XOR the previous key stream with IV'
         * ( S(-1) xor IV' )
         */
        for (int i = 0; i < BLKLEN; i++) {
            f8ctx.S[i] ^= f8ctx.ivAccent[i];
        }

        /*
         * Now XOR (S(n-1) xor IV') with the current counter, then increment 
         * the counter
         */
        f8ctx.S[12] ^= f8ctx.J >> 24;
        f8ctx.S[13] ^= f8ctx.J >> 16;
        f8ctx.S[14] ^= f8ctx.J >> 8;
        f8ctx.S[15] ^= f8ctx.J >> 0;
        f8ctx.J++;

        /*
         * Now compute the new key stream using AES encrypt
         */
        aesCipher.processBlock(f8ctx.S, 0, f8ctx.S, 0);

        /*
         * As the last step XOR the plain text with the key stream to produce
         * the cipher text.
         */
        for (int i = 0; i < len; i++) {
            out[outOff + i] = (byte) (in[inOff + i] ^ f8ctx.S[i]);
        }
    }
}
