/*
* PKCS#11 Object
* (C) 2016 Daniel Neus, Sirrix AG
* (C) 2016 Philipp Weber, Sirrix AG
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#include <botan/p11_object.h>
#include <map>

namespace Botan {

namespace PKCS11 {

AttributeContainer::AttributeContainer(ObjectClass object_class)
   {
   add_class(object_class);
   }

void AttributeContainer::add_class(ObjectClass object_class)
   {
   m_numerics.emplace_back(static_cast< uint64_t >(object_class));
   add_attribute(AttributeType::Class,
                 reinterpret_cast< uint8_t* >(&m_numerics.back()),
                 static_cast<Ulong>(sizeof(ObjectClass)));
   }

void AttributeContainer::add_string(AttributeType attribute, const std::string& value)
   {
   m_strings.push_back(value);
   add_attribute(attribute, reinterpret_cast<const uint8_t*>(m_strings.back().data()), static_cast<Ulong>(value.size()));
   }

void AttributeContainer::add_binary(AttributeType attribute, const uint8_t* value, size_t length)
   {
   m_vectors.push_back(secure_vector<uint8_t>(value, value + length));
   add_attribute(attribute, reinterpret_cast< const uint8_t* >(m_vectors.back().data()), static_cast<Ulong>(length));
   }

void AttributeContainer::add_bool(AttributeType attribute, bool value)
   {
   m_numerics.push_back(value ? True : False);
   add_attribute(attribute, reinterpret_cast< uint8_t* >(&m_numerics.back()), sizeof(Bbool));
   }

void AttributeContainer::add_attribute(AttributeType attribute, const uint8_t* value, uint32_t size)
   {
   bool exists = false;
   // check if the attribute has been added already
   for(auto& existing_attribute : m_attributes)
      {
      if(existing_attribute.type == static_cast< CK_ATTRIBUTE_TYPE >(attribute))
         {
         // remove old entries
         m_strings.erase(std::remove_if(m_strings.begin(), m_strings.end(), [ &existing_attribute ](const std::string& data)
            {
            return data.data() == existing_attribute.pValue;
            }), m_strings.end());

         m_numerics.erase(std::remove_if(m_numerics.begin(), m_numerics.end(), [ &existing_attribute ](const uint64_t& data)
            {
            return &data == existing_attribute.pValue;
            }), m_numerics.end());

         m_vectors.erase(std::remove_if(m_vectors.begin(),
                                        m_vectors.end(), [ &existing_attribute ](const secure_vector<uint8_t>& data)
            {
            return data.data() == existing_attribute.pValue;
            }), m_vectors.end());

         existing_attribute.pValue = const_cast< uint8_t* >(value);
         existing_attribute.ulValueLen = size;
         exists = true;
         break;
         }
      }

   if(!exists)
      {
      m_attributes.push_back(Attribute{ static_cast< CK_ATTRIBUTE_TYPE >(attribute), const_cast< uint8_t* >(value), size });
      }
   }

// ====================================================================================================

ObjectFinder::ObjectFinder(Session& session, const std::vector<Attribute>& search_template)
   : m_session(session), m_search_terminated(false)
   {
   module()->C_FindObjectsInit(m_session.get().handle(),
                               const_cast< Attribute* >(search_template.data()),
                               static_cast<Ulong>(search_template.size()));
   }

ObjectFinder::~ObjectFinder() noexcept
   {
   try
      {
      if(m_search_terminated == false)
         {
         module()->C_FindObjectsFinal(m_session.get().handle(), nullptr);
         }
      }
   catch(...)
      {
      // ignore error during noexcept function
      }
   }

std::vector<ObjectHandle> ObjectFinder::find(uint32_t max_count) const
   {
   std::vector<ObjectHandle> result(max_count);
   Ulong objectCount = 0;
   module()->C_FindObjects(m_session.get().handle(), result.data(), max_count, &objectCount);
   if(objectCount < max_count)
      {
      result.resize(objectCount);
      }
   return result;
   }

void ObjectFinder::finish()
   {
   module()->C_FindObjectsFinal(m_session.get().handle());
   m_search_terminated = true;
   }

// ====================================================================================================

ObjectProperties::ObjectProperties(ObjectClass object_class)
   : AttributeContainer(object_class), m_object_class(object_class)
   {}

// ====================================================================================================

StorageObjectProperties::StorageObjectProperties(ObjectClass object_class)
   : ObjectProperties(object_class)
   {}

// ====================================================================================================

DataObjectProperties::DataObjectProperties()
   : StorageObjectProperties(ObjectClass::Data)
   {}

// ====================================================================================================

CertificateProperties::CertificateProperties(CertificateType cert_type)
   : StorageObjectProperties(ObjectClass::Certificate), m_cert_type(cert_type)
   {
   add_numeric(AttributeType::CertificateType, static_cast< CK_CERTIFICATE_TYPE >(m_cert_type));
   }

// ====================================================================================================

KeyProperties::KeyProperties(ObjectClass object_class, KeyType key_type)
   : StorageObjectProperties(object_class), m_key_type(key_type)
   {
   add_numeric(AttributeType::KeyType, static_cast< CK_ULONG >(m_key_type));
   }

// ====================================================================================================

PublicKeyProperties::PublicKeyProperties(KeyType key_type)
   : KeyProperties(ObjectClass::PublicKey, key_type)
   {}

// ====================================================================================================

PrivateKeyProperties::PrivateKeyProperties(KeyType key_type)
   : KeyProperties(ObjectClass::PrivateKey, key_type)
   {}

// ====================================================================================================

SecretKeyProperties::SecretKeyProperties(KeyType key_type)
   : KeyProperties(ObjectClass::SecretKey, key_type)
   {}

// ====================================================================================================

DomainParameterProperties::DomainParameterProperties(KeyType key_type)
   : StorageObjectProperties(ObjectClass::DomainParameters), m_key_type(key_type)
   {
   add_numeric(AttributeType::KeyType, static_cast< CK_ULONG >(m_key_type));
   }

// ====================================================================================================

Object::Object(Session& session, ObjectHandle handle)
   : m_session(session), m_handle(handle)
   {}

Object::Object(Session& session, const ObjectProperties& obj_props)
   : m_session(session), m_handle(0)
   {
   m_session.get().module()->C_CreateObject(m_session.get().handle(), obj_props.data(), static_cast<Ulong>(obj_props.count()), &m_handle);
   }

secure_vector<uint8_t> Object::get_attribute_value(AttributeType attribute) const
   {
   std::map<AttributeType, secure_vector<uint8_t>> attribute_map = { { attribute, secure_vector<uint8_t>() } };
   module()->C_GetAttributeValue(m_session.get().handle(), m_handle, attribute_map);
   return attribute_map.at(attribute);
   }

void Object::set_attribute_value(AttributeType attribute, const secure_vector<uint8_t>& value) const
   {
   std::map<AttributeType, secure_vector<uint8_t>> attribute_map = { { attribute, value } };
   module()->C_SetAttributeValue(m_session.get().handle(), m_handle, attribute_map);
   }

void Object::destroy() const
   {
   module()->C_DestroyObject(m_session.get().handle(), m_handle);
   }

ObjectHandle Object::copy(const AttributeContainer& modified_attributes) const
   {
   ObjectHandle copied_handle;
   module()->C_CopyObject(m_session.get().handle(), m_handle,
                          modified_attributes.data(), static_cast<Ulong>(modified_attributes.count()),
                          &copied_handle);
   return copied_handle;
   }
}
}
