ISO 7816-4 Padding

using System;

namespace Algorithms.Crypto.Paddings;

/// <summary>
/// <para>
/// ISO 7816-4 padding is a padding scheme that is defined in the ISO/IEC 7816-4 documentation.
/// </para>
/// <para>
/// It is used for adding data to the end of a message that needs to be encrypted or decrypted by a block cipher.
/// </para>
/// ISO 7816-4 padding works as follows:
/// <para>
/// The first byte of the padding is 0x80, which is the hexadecimal representation of the binary value 10000000. This
/// byte indicates the start of the padding.
/// </para>
/// <para>
/// All other bytes of the padding are 0x00, which is the hexadecimal representation of the binary value 00000000. These
/// bytes fill up the remaining space in the last block.
/// </para>
/// <para>
/// The padding can be of any size, from 1 byte to the block size. For example, if the block size is 8 bytes and the
/// message has 5 bytes, then 3 bytes of padding are needed. The padding would be <c>0x80 0x00 0x00</c>.
/// </para>
/// <para>
/// ISO 7816-4 padding is also known as bit padding,because it simply places a single 1 bit after the plaintext, followed
/// by 0 valued bits up to the block size. It works for both byte-oriented and bit-oriented protocols, as it does not
/// depend on any specific character encoding or representation.
/// </para>
/// </summary>
public class Iso7816D4Padding : IBlockCipherPadding
{
    /// <summary>
    /// Adds padding to the input data according to the ISO 7816-4 standard.
    /// </summary>
    /// <param name="inputData">The input data array that needs padding.</param>
    /// <param name="inputOffset">The offset in the input data array where the padding should start.</param>
    /// <returns>The number of bytes added as padding.</returns>
    /// <exception cref="ArgumentException">
    /// Thrown when there is not enough space in the input array for padding or when the input offset is invalid.
    /// </exception>
    public int AddPadding(byte[] inputData, int inputOffset)
    {
        // Calculate the number of padding bytes based on the input data length and offset.
        var code = (byte)(inputData.Length - inputOffset);

        // Check if the padding bytes are valid and fit in the input array.
        if (code == 0 || inputOffset + code > inputData.Length)
        {
            throw new ArgumentException("Not enough space in input array for padding");
        }

        // Set the first padding byte to 80. This marks the start of padding in the ISO 7816-4 standard.
        inputData[inputOffset] = 80;
        inputOffset++;

        // Set the remaining padding bytes to 0.
        while (inputOffset < inputData.Length)
        {
            inputData[inputOffset] = 0;
            inputOffset++;
        }

        // Return the number of padding bytes.
        return code;
    }

    /// <summary>
    /// Removes the padding from the input data array and returns the original data.
    /// </summary>
    /// <param name="inputData">
    /// The input data with ISO 7816-4 padding. Must not be null and must have a valid length and padding.
    /// </param>
    /// <returns>The input data without the padding as a new byte array.</returns>
    /// <exception cref="ArgumentException">
    /// Thrown when the input data has invalid padding.
    /// </exception>
    public byte[] RemovePadding(byte[] inputData)
    {
        // Find the index of the first padding byte by scanning from the end of the input.
        var paddingIndex = inputData.Length - 1;

        // Skip all the padding bytes that are 0.
        while (paddingIndex >= 0 && inputData[paddingIndex] == 0)
        {
            paddingIndex--;
        }

        // Check if the first padding byte is 0x80.
        if (paddingIndex < 0 || inputData[paddingIndex] != 0x80)
        {
            throw new ArgumentException("Invalid padding");
        }

        // Create a new array to store the unpadded data.
        var unpaddedData = new byte[paddingIndex];

        // Copy the unpadded data from the input data to the new array.
        Array.Copy(inputData, 0, unpaddedData, 0, paddingIndex);

        // Return the unpadded data array.
        return unpaddedData;
    }

    /// <summary>
    /// Gets the number of padding bytes in the input data according to the ISO 7816-4 standard.
    /// </summary>
    /// <param name="input">The input data array that has padding.</param>
    /// <returns>The number of padding bytes in the input data.</returns>
    /// <exception cref="ArgumentException"> Thrown when the input data has invalid padding.</exception>
    public int GetPaddingCount(byte[] input)
    {
        // Initialize the index of the first padding byte to -1.
        var paddingStartIndex = -1;

        // Initialize a mask to indicate if the current byte is still part of the padding.
        var stillPaddingMask = -1;

        // Initialize the current index to the end of the input data.
        var currentIndex = input.Length;

        // Loop backwards through the input data.
        while (--currentIndex >= 0)
        {
            // Get the current byte as an unsigned integer.
            var currentByte = input[currentIndex] & 0xFF;

            // Compute a mask to indicate if the current byte is 0x00.
            var isZeroMask = (currentByte - 1) >> 31;

            // Compute a mask to indicate if the current byte is 0x80.
            var isPaddingStartMask = ((currentByte ^ 0x80) - 1) >> 31;

            // Update the index of the first padding byte using bitwise operations.
            // If the current byte is 0x80 and still part of the padding, set the index to the current index.
            // Otherwise, keep the previous index.
            paddingStartIndex ^= (currentIndex ^ paddingStartIndex) & (stillPaddingMask & isPaddingStartMask);

            // Update the mask to indicate if the current byte is still part of the padding using bitwise operations.
            // If the current byte is 0x00, keep the previous mask.
            // Otherwise, set the mask to 0.
            stillPaddingMask &= isZeroMask;
        }

        // Check if the index of the first padding byte is valid.
        if (paddingStartIndex < 0)
        {
            throw new ArgumentException("Pad block corrupted");
        }

        // Return the number of padding bytes.
        return input.Length - paddingStartIndex;
    }
}