def get_adjusted_download_range_and_offset(
        start: int,
        end: int,
        length: int,
        encryption_data: Optional[_EncryptionData]) -> Tuple[Tuple[int, int], Tuple[int, int]]:
    """
    Gets the new download range and offsets into the decrypted data for
    the given user-specified range. The new download range will include all
    the data needed to decrypt the user-provided range and will include only
    full encryption regions.

    The offsets returned will be the offsets needed to fetch the user-requested
    data out of the full decrypted data. The end offset is different based on the
    encryption version. For V1, the end offset is offset from the end whereas for
    V2, the end offset is the ending index into the stream.
    V1: decrypted_data[start_offset : len(decrypted_data) - end_offset]
    V2: decrypted_data[start_offset : end_offset]

    :param int start: The user-requested start index.
    :param int end: The user-requested end index.
    :param int length: The user-requested length. Only used for V1.
    :param Optional[_EncryptionData] encryption_data: The encryption data to determine version and sizes.
    :return: (new start, new end), (start offset, end offset)
    :rtype: Tuple[Tuple[int, int], Tuple[int, int]]
    """
    start_offset, end_offset = 0, 0
    if encryption_data is None:
        return (start, end), (start_offset, end_offset)

    if encryption_data.encryption_agent.protocol == _ENCRYPTION_PROTOCOL_V1:
        if start is not None:
            # Align the start of the range along a 16 byte block
            start_offset = start % 16
            start -= start_offset

            # Include an extra 16 bytes for the IV if necessary
            # Because of the previous offsetting, start_range will always
            # be a multiple of 16.
            if start > 0:
                start_offset += 16
                start -= 16

        if length is not None:
            # Align the end of the range along a 16 byte block
            end_offset = 15 - (end % 16)
            end += end_offset

    elif encryption_data.encryption_agent.protocol == _ENCRYPTION_PROTOCOL_V2:
        start_offset, end_offset = 0, end

        nonce_length = encryption_data.encrypted_region_info.nonce_length
        data_length = encryption_data.encrypted_region_info.data_length
        tag_length = encryption_data.encrypted_region_info.tag_length
        region_length = nonce_length + data_length + tag_length
        requested_length = end - start

        if start is not None:
            # Find which data region the start is in
            region_num = start // data_length
            # The start of the data region is different from the start of the encryption region
            data_start = region_num * data_length
            region_start = region_num * region_length
            # Offset is based on data region
            start_offset = start - data_start
            # New start is the start of the encryption region
            start = region_start

        if end is not None:
            # Find which data region the end is in
            region_num = end // data_length
            end_offset = start_offset + requested_length + 1
            # New end is the end of the encryption region
            end = (region_num * region_length) + region_length - 1

    return (start, end), (start_offset, end_offset)

    Gets the new download range and offsets into the decrypted data for
    the given user-specified range. The new download range will include all
    the data needed to decrypt the user-provided range and will include only
    full encryption regions.

    The offsets returned will be the offsets needed to fetch the user-requested
    data out of the full decrypted data. The end offset is different based on the
    encryption version. For V1, the end offset is offset from the end whereas for
    V2, the end offset is the ending index into the stream.
    V1: decrypted_data[start_offset : len(decrypted_data) - end_offset]
    V2: decrypted_data[start_offset : end_offset]

    :param int start: The user-requested start index.
    :param int end: The user-requested end index.
    :param int length: The user-requested length. Only used for V1.
    :param Optional[_EncryptionData] encryption_data: The encryption data to determine version and sizes.
    :return: (new start, new end), (start offset, end offset)
    :rtype: Tuple[Tuple[int, int], Tuple[int, int]]
    
Solutions/CyberArkAudit/Data Connectors/.python_packages/lib/site-packages/azure/storage/blob/_encryption.py