# Copyright 2016 Tesora, Inc.
# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.
#

from Crypto import Random
import mock
import six

from trove.common import crypto_utils
from trove.tests.unittests import trove_testtools


class TestEncryptUtils(trove_testtools.TestCase):

    def setUp(self):
        super(TestEncryptUtils, self).setUp()

    def tearDown(self):
        super(TestEncryptUtils, self).tearDown()

    def test_encode_decode_string(self):
        random_data = bytearray(Random.new().read(12))
        data = [b'abc', b'numbers01234', b'\x00\xFF\x00\xFF\xFF\x00',
                random_data, u'Unicode:\u20ac']

        for datum in data:
            encoded_data = crypto_utils.encode_data(datum)
            decoded_data = crypto_utils.decode_data(encoded_data)
            if isinstance(datum, six.text_type):
                decoded_data = decoded_data.decode('utf-8')
            self. assertEqual(datum, decoded_data,
                              "Encode/decode failed")

    def test_pad_unpad(self):
        for size in range(1, 100):
            data_str = b'a' * size
            padded_str = crypto_utils.pad_for_encryption(
                data_str, crypto_utils.IV_BIT_COUNT)
            self.assertEqual(0, len(padded_str) % crypto_utils.IV_BIT_COUNT,
                             "Padding not successful")
            unpadded_str = crypto_utils.unpad_after_decryption(padded_str)
            self.assertEqual(data_str, unpadded_str,
                             "String mangled after pad/unpad")

    def test_encryp_decrypt(self):
        key = 'my_secure_key'
        for size in range(1, 100):
            orig_data = Random.new().read(size)
            orig_encoded = crypto_utils.encode_data(orig_data)
            encrypted = crypto_utils.encrypt_data(orig_encoded, key)
            encoded = crypto_utils.encode_data(encrypted)
            decoded = crypto_utils.decode_data(encoded)
            decrypted = crypto_utils.decrypt_data(decoded, key)
            final_decoded = crypto_utils.decode_data(decrypted)

            self.assertEqual(orig_data, final_decoded,
                             "Decrypted data did not match original")

    def test_encrypt(self):
        # test encrypt() with an hardcoded IV
        key = 'my_secure_key'
        salt = b'x' * crypto_utils.IV_BIT_COUNT

        with mock.patch('Crypto.Random.new') as mock_random:
            mock_random.return_value.read.return_value = salt

            for orig_data, expected in (
                # byte string
                (b'Hello World!',
                 'eHh4eHh4eHh4eHh4eHh4eF5RK6VdDrAWl4Th1mNG2eps+VB2BouFRiY2Wa'
                    'P/RRPT'),

                # Unicoded string (encoded to UTF-8)
                (u'Unicode:\u20ac',
                 'eHh4eHh4eHh4eHh4eHh4eAMsI5YsrtMNAPJfVF0j9NegXML7OsJ0LuAy66'
                    'LKv5F4'),
            ):
                orig_encoded = crypto_utils.encode_data(orig_data)
                encrypted = crypto_utils.encrypt_data(orig_encoded, key)
                encoded = crypto_utils.encode_data(encrypted)
                self.assertEqual(expected, encoded)

    def test_decrypt(self):
        key = 'my_secure_key'

        for encoded, expected in (
            # byte string: b'Hello World!'
            ('ZUhoNGVIaDRlSGg0ZUhoNL9PmM70hVcQ7j/kYF7Pw+BT7VSfsht0VsCIxy'
                'KNN0NH',
             b'Hello World!'),

            # Unicoded string: u'Unicode:\u20ac'
            ('ZUhoNGVIaDRlSGg0ZUhoNIHZLIuIcQCRwWY7PR2y7JcqoDf4ViqXIfh0uE'
                'Rbg9BA',
             b'Unicode:\xe2\x82\xac'),
        ):
            decoded = crypto_utils.decode_data(encoded)
            decrypted = crypto_utils.decrypt_data(decoded, key)
            final_decoded = crypto_utils.decode_data(decrypted)
            self.assertEqual(expected, final_decoded)
