/* Rijndael (AES) for GnuPG - PowerPC Vector Crypto AES implementation
 * Copyright 2021- IBM Inc. All rights reserved
 *
 * This file is part of Libgcrypt.
 *
 * Libgcrypt 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.
 *
 * Libgcrypt 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, see <http://www.gnu.org/licenses/>.
 *
 * Alternatively, this code may be used in OpenSSL from The OpenSSL Project,
 * and Cryptogams by Andy Polyakov, and if made part of a release of either
 * or both projects, is thereafter dual-licensed under the license said project
 * is released under.
 */

#include <config.h>

#include "rijndael-internal.h"
#include "cipher-internal.h"
#include "bufhelp.h"

#ifdef USE_PPC_CRYPTO_WITH_PPC9LE

#include "simd-common-ppc.h"


extern size_t _gcry_ppc10_aes_gcm_encrypt (const void *inp, void *out,
                                           size_t len,
                                           const unsigned char *key,
                                           unsigned char iv[16], void *Xip);
extern size_t _gcry_ppc10_aes_gcm_decrypt (const void *inp, void *out,
                                           size_t len,
                                           const unsigned char *key,
                                           unsigned char iv[16], void *Xip);

size_t
_gcry_aes_p10le_gcm_crypt(gcry_cipher_hd_t c, void *outbuf_arg,
                          const void *inbuf_arg, size_t nblocks, int encrypt)
{
  RIJNDAEL_context *ctx = (void *)&c->context.c;
  unsigned char *rk = (unsigned char *) ctx->u1.keyschedule;
  unsigned char *gcm_table = (unsigned char *) c->u_mode.gcm.gcm_table;
  unsigned char *iv = c->u_ctr.ctr;
  unsigned char *Xi = c->u_mode.gcm.u_tag.tag;
  int s = 0;
  int ndone = 0;
  int ctr_reset = 0;
  size_t len = nblocks * GCRY_GCM_BLOCK_LEN;
  u64 blocks_unused;
  u64 nb = nblocks;
  u64 next_ctr = 0;
  unsigned char ctr_saved[12];
  unsigned char *inp = (unsigned char *) inbuf_arg;
  unsigned char *out = (unsigned char *) outbuf_arg;

  /*
   * This is what the aes-gcm asembly code expects some input parameters.
   *
   *   - Number of rounds is at 480 offset from rk (rk->rounds)
   *   - Xi at 256 offset from gcm_table
   */
  gcry_assert (sizeof(c->u_mode.gcm.gcm_table) >= 256 + 16);
  buf_cpy (gcm_table+256, Xi, 16);
  buf_cpy (ctr_saved, c->u_ctr.ctr, 12);

  while (nb)
    {
      blocks_unused = (u64) 0xffffffffU + 1 - (u64) buf_get_be32 (iv + 12);
      if (nb > blocks_unused)
        {
          len = blocks_unused * GCRY_GCM_BLOCK_LEN;
          nb -= blocks_unused;
          next_ctr = blocks_unused;
          ctr_reset = 1;
        }
      else
        {
          len = nb * GCRY_GCM_BLOCK_LEN;
          next_ctr = nb;
          nb = 0;
        }

      if (encrypt)
        s = _gcry_ppc10_aes_gcm_encrypt((const void *) inp, (void *) out, len,
                                        (const unsigned char *) rk, iv,
                                        (void *) gcm_table);
      else
        s = _gcry_ppc10_aes_gcm_decrypt((const void *) inp, (void *) out, len,
                                        (const unsigned char *) rk, iv,
                                        (void *) gcm_table);

      cipher_block_add(c->u_ctr.ctr, next_ctr, GCRY_GCM_BLOCK_LEN);
      if (ctr_reset)
        {
          ctr_reset = 0;
          inp += len;
          out += len;
        }
      buf_cpy (c->u_ctr.ctr, ctr_saved, 12);
      ndone += s;
    }
  buf_cpy (Xi, gcm_table+256, 16);

  /*
   * Return number of blocks done.
   */
  s = ndone / GCRY_GCM_BLOCK_LEN;
  s = nblocks - s;

  clear_vec_regs();

  return ( s );
}

#endif /* USE_PPC_CRYPTO_WITH_PPC9LE */
