/* LibTomCrypt, modular cryptographic library -- Tom St Denis */
/* SPDX-License-Identifier: Unlicense */
#include "tomcrypt_private.h"

#ifdef LTC_PADDING

/**
   Determine the to-be-padded length.

   @param length     [in/out] The size of the data before/after padding
   @param mode       Mask of (LTC_PAD_xxx | block_length)
   @return CRYPT_OK on success
*/
static int s_padding_padded_length(unsigned long *length, unsigned long mode)
{
   enum padding_type padding;
   unsigned char pad, block_length, r, t;

   LTC_ARGCHK(length != NULL);

   block_length = mode & 0xff;
   padding = mode & LTC_PAD_MASK;
   r = *length % block_length;

   switch (padding) {
      case LTC_PAD_ZERO:
         if (r == 0) {
            t = 0;
            break;
         }
         LTC_FALLTHROUGH; /* FALLTHROUGH */
      case LTC_PAD_PKCS7:
      case LTC_PAD_ONE_AND_ZERO:
      case LTC_PAD_ZERO_ALWAYS:
      case LTC_PAD_SSH:
         t = 1;
         break;
#ifdef LTC_RNG_GET_BYTES
      case LTC_PAD_ISO_10126:
         do {
            if (rng_get_bytes(&t, sizeof(t), NULL) != sizeof(t)) {
               return CRYPT_ERROR_READPRNG;
            }
            t %= (256 / block_length);
         } while (t == 0);
         break;
#endif
      case LTC_PAD_ANSI_X923:
         if (block_length != 16) {
            return CRYPT_INVALID_ARG;
         }
         t = 1;
         break;
      default:
         return CRYPT_INVALID_ARG;
   }

   pad = (t * block_length) - r;

   if ((pad == 0) && (padding != LTC_PAD_ZERO)) {
      pad = block_length;
   }

   *length += pad;

   return CRYPT_OK;
}

/**
   Add padding to data.

      This pads your data.

   @param data          The data to depad
   @param length        The size of the data before padding
   @param padded_length [in/out] The size of the data available/after padding
   @param mode          One of the LTC_PAD_xx flags
   @return CRYPT_OK on success
*/
int padding_pad(unsigned char *data, unsigned long length, unsigned long* padded_length, unsigned long mode)
{
   unsigned long l, n;
   enum padding_type type;
   int err;
   unsigned char diff, pad;

   LTC_ARGCHK(data          != NULL);
   LTC_ARGCHK(padded_length != NULL);

   l = length;
   if ((err = s_padding_padded_length(&l, mode)) != CRYPT_OK) {
      return err;
   }

   type = mode & LTC_PAD_MASK;

   if (*padded_length < l) {
#ifdef LTC_RNG_GET_BYTES
      if (type != LTC_PAD_ISO_10126) {
         *padded_length = l;
      } else {
         *padded_length = length + 256;
      }
#else
      *padded_length = l;
#endif
      return CRYPT_BUFFER_OVERFLOW;
   }

   if (l - length > 255) return CRYPT_INVALID_ARG;
   diff = (unsigned char)(l - length);

   switch (type) {
      case LTC_PAD_PKCS7:
         XMEMSET(&data[length], diff, diff);
         break;
#ifdef LTC_RNG_GET_BYTES
      case LTC_PAD_ISO_10126:
         if (rng_get_bytes(&data[length], diff-1u, NULL) != diff-1u) {
            return CRYPT_ERROR_READPRNG;
         }
         data[l-1] =  diff;
         break;
#endif
      case LTC_PAD_ANSI_X923:
         XMEMSET(&data[length], 0, diff-1);
         data[l-1] =  diff;
         break;
      case LTC_PAD_SSH:
         pad = 0x1;
         for (n = length; n < l; ++n) {
            data[n] = pad++;
         }
         break;
      case LTC_PAD_ONE_AND_ZERO:
         XMEMSET(&data[length + 1], 0, diff);
         data[length] =  0x80;
         break;
      case LTC_PAD_ZERO:
      case LTC_PAD_ZERO_ALWAYS:
         XMEMSET(&data[length], 0, diff);
         break;
      default:
         return CRYPT_INVALID_ARG;
   }
   *padded_length = l;

   return CRYPT_OK;
}

#endif
