text: // Copyright (c) Microsoft. All rights reserved.
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
  
  #include <stdlib.h>
  
  #include "azure_c_shared_utility/gballoc.h"
  #include "azure_c_shared_utility/tlsio_options.h"
  #include "azure_c_shared_utility/shared_util_options.h"
  #include "azure_c_shared_utility/xlogging.h"
  #include "azure_c_shared_utility/crt_abstractions.h"
  
  
  // Initialize the TLSIO_OPTIONS struct
  void tlsio_options_initialize(TLSIO_OPTIONS* options, int supported_options)
  {
      // Using static function rules, so 'options' is not checked for NULL
      //
      // The supported_options value does not need validation because undefined bits are
      // ignored, while any valid missing bits result in an "option not supported" error
      // that will show up in unit testing.
      options->supported_options = supported_options;
      options->trusted_certs = NULL;
      options->x509_type = TLSIO_OPTIONS_x509_TYPE_UNSPECIFIED;
      options->x509_cert = NULL;
      options->x509_key = NULL;
  }
  
  static int set_and_validate_x509_type(TLSIO_OPTIONS* options, TLSIO_OPTIONS_x509_TYPE x509_type)
  {
      int result;
      if ((options->supported_options & x509_type) == 0)
      {
          // This case also rejects the nonsensical TLSIO_OPTIONS_x509_TYPE_UNSPECIFIED
          LogError("Unsupported x509 type: %d", x509_type);
          result = MU_FAILURE;
      }
      else if (options->x509_type == TLSIO_OPTIONS_x509_TYPE_UNSPECIFIED)
      {
          // Initial type setting
          options->x509_type = x509_type;
          result = 0;
      }
      else if (options->x509_type != x509_type)
      {
          LogError("Supplied x509 type conflicts with previously set x509");
          result = MU_FAILURE;
      }
      else
      {
          // The types match okay
          result = 0;
      }
  
      return result;
  }
  
  void tlsio_options_release_resources(TLSIO_OPTIONS* options)
  {
      if (options != NULL)
      {
          free((void*)options->trusted_certs);
          free((void*)options->x509_cert);
          free((void*)options->x509_key);
      }
      else
      {
          LogError("NULL options");
      }
  }
  
  static bool is_supported_string_option(const char* name)
  {
      return
          (strcmp(name, OPTION_TRUSTED_CERT) == 0) ||
          (strcmp(name, OPTION_OPENSSL_CIPHER_SUITE) == 0) ||
          (strcmp(name, OPTION_OPENSSL_ENGINE) == 0) ||
          (strcmp(name, OPTION_OPENSSL_PRIVATE_KEY_TYPE) == 0) ||
          (strcmp(name, SU_OPTION_X509_CERT) == 0) ||
          (strcmp(name, SU_OPTION_X509_PRIVATE_KEY) == 0) ||
          (strcmp(name, OPTION_X509_ECC_CERT) == 0) ||
          (strcmp(name, OPTION_X509_ECC_KEY) == 0);
  }
  
  TLSIO_OPTIONS_RESULT tlsio_options_destroy_option(const char* name, const void* value)
  {
      TLSIO_OPTIONS_RESULT result;
      if (name == NULL || value == NULL)
      {
          LogError("NULL parameter: name: %p, value: %p", name, value);
          result = TLSIO_OPTIONS_RESULT_ERROR;
      }
      else if (is_supported_string_option(name))
      {
          free((void*)value);
          result = TLSIO_OPTIONS_RESULT_SUCCESS;
      }
      else
      {
          result = TLSIO_OPTIONS_RESULT_NOT_HANDLED;
      }
      return result;
  }
  
  TLSIO_OPTIONS_RESULT tlsio_options_clone_option(const char* name, const void* value, void** out_value)
  {
      TLSIO_OPTIONS_RESULT result;
  
      if (name == NULL || value == NULL || out_value == NULL)
      {
          LogError("NULL parameter: name: %p, value: %p, out_value: %p",
              name, value, out_value);
          result = TLSIO_OPTIONS_RESULT_ERROR;
      }
      else if (is_supported_string_option(name))
      {
          *out_value = NULL;
          if (mallocAndStrcpy_s((char**)out_value, value) != 0)
          {
              LogError("unable to mallocAndStrcpy_s option value");
              result = TLSIO_OPTIONS_RESULT_ERROR;
          }
          else
          {
              result = TLSIO_OPTIONS_RESULT_SUCCESS;
          }
      }
      else
      {
          result = TLSIO_OPTIONS_RESULT_NOT_HANDLED;
      }
      return result;
  }
  
  TLSIO_OPTIONS_RESULT tlsio_options_set(TLSIO_OPTIONS* options,
      const char* optionName, const void* value)
  {
      TLSIO_OPTIONS_RESULT result;
      char* copied_value = NULL;
  
      if (options == NULL || optionName == NULL || value == NULL)
      {
          LogError("NULL parameter: options: %p, optionName: %p, value: %p",
              options, optionName, value);
          result = TLSIO_OPTIONS_RESULT_ERROR;
      }
      else if (!is_supported_string_option(optionName))
      {
          result = TLSIO_OPTIONS_RESULT_NOT_HANDLED;
      }
      else if(mallocAndStrcpy_s(&copied_value, value) != 0)
      {
          LogError("unable to mallocAndStrcpy_s option value");
          result = TLSIO_OPTIONS_RESULT_ERROR;
      }
      else if (strcmp(OPTION_TRUSTED_CERT, optionName) == 0)
      {
          if ((options->supported_options & TLSIO_OPTION_BIT_TRUSTED_CERTS) == 0)
          {
              LogError("Trusted certs option not supported");
              result = TLSIO_OPTIONS_RESULT_ERROR;
          }
          else if (options->trusted_certs != NULL)
          {
              LogError("unable to set trusted cert option more than once");
              result = TLSIO_OPTIONS_RESULT_ERROR;
          }
          else
          {
              options->trusted_certs = copied_value;
              result = TLSIO_OPTIONS_RESULT_SUCCESS;
          }
      }
      else if (strcmp(SU_OPTION_X509_CERT, optionName) == 0 || strcmp(OPTION_X509_ECC_CERT, optionName) == 0)
      {
          TLSIO_OPTIONS_x509_TYPE this_type = (strcmp(SU_OPTION_X509_CERT, optionName) == 0) ? TLSIO_OPTIONS_x509_TYPE_RSA : TLSIO_OPTIONS_x509_TYPE_ECC;
          if (options->x509_cert != NULL)
          {
              LogError("unable to set x509 cert more than once");
              result = TLSIO_OPTIONS_RESULT_ERROR;
          }
          else if (set_and_validate_x509_type(options, this_type) != 0)
          {
              // Error logged by helper
              result = TLSIO_OPTIONS_RESULT_ERROR;
          }
          else
          {
              options->x509_cert = copied_value;
              result = TLSIO_OPTIONS_RESULT_SUCCESS;
          }
      }
      else if (strcmp(SU_OPTION_X509_PRIVATE_KEY, optionName) == 0 || strcmp(OPTION_X509_ECC_KEY, optionName) == 0)
      {
          TLSIO_OPTIONS_x509_TYPE this_type = (strcmp(SU_OPTION_X509_PRIVATE_KEY, optionName) == 0) ? TLSIO_OPTIONS_x509_TYPE_RSA : TLSIO_OPTIONS_x509_TYPE_ECC;
          if (options->x509_key != NULL)
          {
              LogError("unable to set x509 key more than once");
              result = TLSIO_OPTIONS_RESULT_ERROR;
          }
          else if (set_and_validate_x509_type(options, this_type) != 0)
          {
              // Error logged by helper
              result = TLSIO_OPTIONS_RESULT_ERROR;
          }
          else
          {
              options->x509_key = copied_value;
              result = TLSIO_OPTIONS_RESULT_SUCCESS;
          }
      }
      else
      {
          // This is logically impossible due to earlier tests, so just quiet the compiler
          result = TLSIO_OPTIONS_RESULT_ERROR;
      }
  
      if (result != TLSIO_OPTIONS_RESULT_SUCCESS)
      {
          free(copied_value);
      }
  
      return result;
  }
  
  // A helper that works if the tlsio does not use any extra options
  static void* local_clone_option(const char* name, const void* value)
  {
      void* result = NULL;
      if (tlsio_options_clone_option(name, value, &result) != TLSIO_OPTIONS_RESULT_SUCCESS)
      {
          LogError("Unexpected local_clone_option failure");
      }
      return result;
  }
  
  // A helper that works if the tlsio does not use any extra options
  void local_destroy_option(const char* name, const void* value)
  {
      if (tlsio_options_destroy_option(name, value) != TLSIO_OPTIONS_RESULT_SUCCESS)
      {
          LogError("Unexpected local_destroy_option failure");
      }
  }
  
  OPTIONHANDLER_HANDLE tlsio_options_retrieve_options(TLSIO_OPTIONS* options, pfSetOption setOption)
  {
      return tlsio_options_retrieve_options_ex(options, local_clone_option, local_destroy_option, setOption);
  }
  
  
  OPTIONHANDLER_HANDLE tlsio_options_retrieve_options_ex(TLSIO_OPTIONS* options,
      pfCloneOption cloneOption, pfDestroyOption destroyOption, pfSetOption setOption)
  {
      OPTIONHANDLER_HANDLE result;
      if (options == NULL || cloneOption == NULL || destroyOption == NULL || setOption == NULL)
      {
          LogError("Null parameter in options: %p, cloneOption: %p, destroyOption: %p, setOption: %p",
              options, cloneOption, destroyOption, setOption);
          result = NULL;
      }
      else
      {
          result = OptionHandler_Create(cloneOption, destroyOption, setOption);
          if (result == NULL)
          {
              LogError("OptionHandler_Create failed");
              /*return as is*/
          }
          else if (
                  (options->trusted_certs != NULL) &&
                  (OptionHandler_AddOption(result, OPTION_TRUSTED_CERT, options->trusted_certs) != OPTIONHANDLER_OK)
                  )
          {
              LogError("unable to save TrustedCerts option");
              OptionHandler_Destroy(result);
              result = NULL;
          }
          else if (options->x509_type != TLSIO_OPTIONS_x509_TYPE_UNSPECIFIED)
          {
              const char* x509_cert_option;
              const char* x509_key_option;
              if (options->x509_type == TLSIO_OPTIONS_x509_TYPE_ECC)
              {
                  x509_cert_option = OPTION_X509_ECC_CERT;
                  x509_key_option = OPTION_X509_ECC_KEY;
              }
              else
              {
                  x509_cert_option = SU_OPTION_X509_CERT;
                  x509_key_option = SU_OPTION_X509_PRIVATE_KEY;
              }
              if (
                  (options->x509_cert != NULL) &&
                  (OptionHandler_AddOption(result, x509_cert_option, options->x509_cert) != OPTIONHANDLER_OK)
                  )
              {
                  LogError("unable to save x509 cert option");
                  OptionHandler_Destroy(result);
                  result = NULL;
              }
              else if (
                  (options->x509_key != NULL) &&
                  (OptionHandler_AddOption(result, x509_key_option, options->x509_key) != OPTIONHANDLER_OK)
                  )
              {
                  LogError("unable to save x509 key option");
                  OptionHandler_Destroy(result);
                  result = NULL;
              }
          }
      }
  
      return result;
  }
  
  