Decrypt ciphertext using an oracle function that returns ``True`` if the
    provided ciphertext is correctly PKCS#7 padded after decryption. The
    cipher needs to operate in CBC mode.

    Args:
        oracle(callable): The oracle function. Will be called repeatedly with
            a chunk of ciphertext.
        ciphertext(bytes): The data to decrypt. Should include the IV at the
            start.
        known_prefix(bytes): If the start of the plaintext is known, it can be
            provided to skip decrypting the known prefix.
        known_suffix(bytes): If the end of the plaintext is known, it can be
            provided to skip decrypting the known suffix. Should include
            padding.
        block_size(int): The cipher's block size in bits.
        alphabet(bytes): Optimize decryption if you know which characters the
            plaintext will consist of.
        pool(multiprocessing.Pool): A multiprocessing pool to use to
            parallelize the decryption. This pool is used to call the oracle
            function. Fairly heavy due to the required inter-process state
            synchronization. If ``None`` (the default), no multiprocessing
            will be used.
        block_pool(multiprocessing.Pool): A multiprocessing pool to use to
            parallelize the decryption. This pool is used to decrypt entire
            blocks in parallel. When decrypting ciphertext consisting of
            multiple blocks, it is usually more efficient than using the
            ``pool`` argument. If ``None`` (the default), no multiprocessing
            will be used.
        progress(callable): A callable that will be called each time a new
            byte is decrypted. Is called with the positition of the character
            in the plaintext result and the character itself.

    Returns:
        bytes: The decrypted data with its PKCS#7 padding stripped.

    Raises:
        RuntimeError: Raised if the oracle behaves unpredictable.

    Example:
        >>> from pwny import *
        >>> with multiprocessing.Pool(5) as pool:
        >>>     print(padding_oracle_decrypt(oracle_function, encrypted_data, pool=pool))
        b'decrypted data'

def padding_oracle_decrypt(oracle, ciphertext, known_prefix=b'', known_suffix=b'', block_size=128,
                           alphabet=None, pool=None, block_pool=None, progress=None):
    """
    Decrypt ciphertext using an oracle function that returns ``True`` if the
    provided ciphertext is correctly PKCS#7 padded after decryption. The
    cipher needs to operate in CBC mode.

    Args:
        oracle(callable): The oracle function. Will be called repeatedly with
            a chunk of ciphertext.
        ciphertext(bytes): The data to decrypt. Should include the IV at the
            start.
        known_prefix(bytes): If the start of the plaintext is known, it can be
            provided to skip decrypting the known prefix.
        known_suffix(bytes): If the end of the plaintext is known, it can be
            provided to skip decrypting the known suffix. Should include
            padding.
        block_size(int): The cipher's block size in bits.
        alphabet(bytes): Optimize decryption if you know which characters the
            plaintext will consist of.
        pool(multiprocessing.Pool): A multiprocessing pool to use to
            parallelize the decryption. This pool is used to call the oracle
            function. Fairly heavy due to the required inter-process state
            synchronization. If ``None`` (the default), no multiprocessing
            will be used.
        block_pool(multiprocessing.Pool): A multiprocessing pool to use to
            parallelize the decryption. This pool is used to decrypt entire
            blocks in parallel. When decrypting ciphertext consisting of
            multiple blocks, it is usually more efficient than using the
            ``pool`` argument. If ``None`` (the default), no multiprocessing
            will be used.
        progress(callable): A callable that will be called each time a new
            byte is decrypted. Is called with the positition of the character
            in the plaintext result and the character itself.

    Returns:
        bytes: The decrypted data with its PKCS#7 padding stripped.

    Raises:
        RuntimeError: Raised if the oracle behaves unpredictable.

    Example:
        >>> from pwny import *
        >>> with multiprocessing.Pool(5) as pool:
        >>>     print(padding_oracle_decrypt(oracle_function, encrypted_data, pool=pool))
        b'decrypted data'
    """

    block_len = block_size // 8
    assert len(ciphertext) % block_len == 0 and len(ciphertext) >= 2 * block_len

    known_prefix = bytearray(known_prefix)
    known_suffix = bytearray(known_suffix)

    if alphabet is None:
        alphabet = bytearray(range(256))

    if block_pool is not None:
        map_func = block_pool.imap
    else:
        map_func = map

    plaintext = bytearray()

    decrypt_func = functools.partial(decrypt_block, oracle, block_len, alphabet, pool, progress)
    for plain in map_func(decrypt_func, block_pairs(block_len, bytearray(ciphertext), known_prefix, known_suffix)):
        plaintext[0:0] = plain

    return bytes(plaintext[:-plaintext[-1]])