ResponseCode OpenSSLConnection::SetSocketToNonBlocking() {
            int status;
            ResponseCode ret_val = ResponseCode::SUCCESS;
#if defined(WIN32) || defined(WIN64)
            u_long flag = 1L;
            status = ioctlsocket(server_tcp_socket_fd_, FIONBIO, &flag);
            if (0 > status) {
                AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG, "ioctlsocket - %s", strerror(errno));
                ret_val = ResponseCode::NETWORK_TCP_CONNECT_ERROR;
            }
#else
            int flags = fcntl(server_tcp_socket_fd_, F_GETFL, 0);
            // set underlying socket to non blocking
            if (0 > flags) {
                ret_val = ResponseCode::NETWORK_TCP_CONNECT_ERROR;
            }

            status = fcntl(server_tcp_socket_fd_, F_SETFL, flags | O_NONBLOCK);
            if (0 > status) {
                AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG, "fcntl - %s", strerror(errno));
                ret_val = ResponseCode::NETWORK_TCP_CONNECT_ERROR;
            }
#endif

            return ret_val;
        }
        ResponseCode OpenSSLConnection::AttemptConnect() {
            ResponseCode ret_val = ResponseCode::FAILURE;
            int rc = 0;
            fd_set readFds;
            fd_set writeFds;
            struct timeval timeout = {tls_handshake_timeout_.tv_sec, tls_handshake_timeout_.tv_usec};
            int errorCode = 0;
            int select_retCode = 0;

            do {
                rc = SSL_connect(p_ssl_handle_);

                if (1 == rc) { //1 = SSL_CONNECTED, <= 0 is Error
                    ret_val = ResponseCode::SUCCESS;
                    break;
                }

                errorCode = SSL_get_error(p_ssl_handle_, rc);

                if (SSL_ERROR_WANT_READ == errorCode) {
                    FD_ZERO(&readFds);
                    FD_SET(server_tcp_socket_fd_, &readFds);
                    select_retCode = select(server_tcp_socket_fd_ + 1, &readFds, NULL, NULL, &timeout);
                    if (0 == select_retCode) { // 0 == SELECT_TIMEOUT
                        AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG, " SSL Connect time out while waiting for read");
                        ret_val = ResponseCode::NETWORK_SSL_CONNECT_TIMEOUT_ERROR;
                    } else if (-1 == select_retCode) { // -1 == SELECT_ERROR
                        AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG, " SSL Connect Select error for read %d", select_retCode);
                        ret_val = ResponseCode::NETWORK_SSL_CONNECT_ERROR;
                    }
                } else if (SSL_ERROR_WANT_WRITE == errorCode) {
                    FD_ZERO(&writeFds);
                    FD_SET(server_tcp_socket_fd_, &writeFds);
                    select_retCode = select(server_tcp_socket_fd_ + 1, NULL, &writeFds, NULL, &timeout);
                    if (0 == select_retCode) { // 0 == SELECT_TIMEOUT
                        AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG, " SSL Connect time out while waiting for write");
                        ret_val = ResponseCode::NETWORK_SSL_CONNECT_TIMEOUT_ERROR;
                    } else if (-1 == select_retCode) { // -1 == SELECT_ERROR
                        AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG,
                                      " SSL Connect Select error for write %d",
                                      select_retCode);
                        ret_val = ResponseCode::NETWORK_SSL_CONNECT_ERROR;
                    }
                } else {
                    ret_val = ResponseCode::NETWORK_SSL_CONNECT_ERROR;
                }

            } while (ResponseCode::NETWORK_SSL_CONNECT_ERROR != ret_val &&
                ResponseCode::NETWORK_SSL_CONNECT_TIMEOUT_ERROR != ret_val);

            return ret_val;
        }
        ResponseCode OpenSSLConnection::Initialize() {
#ifdef WIN32
            // TODO : Check if it is possible to replace this with std::call_once
            WSADATA wsa_data;
            int result;
            bool was_wsa_initialized = true;
            int s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
            if(INVALID_SOCKET == s) {
                if(WSANOTINITIALISED == WSAGetLastError()) {
                    was_wsa_initialized = false;
                }
            } else {
                closesocket(s);
            }

            if(!was_wsa_initialized) {
                // Initialize Winsock
                result = WSAStartup(MAKEWORD(2, 2), &wsa_data);
                if(0 != result) {
                    AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG, "WSAStartup failed: %d", result);
                    return ResponseCode::NETWORK_SSL_INIT_ERROR;
                }
            }
#endif

            const SSL_METHOD *method;

            OpenSSL_add_all_algorithms();
            ERR_load_BIO_strings();
            ERR_load_crypto_strings();
            SSL_load_error_strings();

            if (SSL_library_init() < 0) {
                return ResponseCode::NETWORK_SSL_INIT_ERROR;
            }

            method = TLSv1_2_method();

            if ((p_ssl_context_ = SSL_CTX_new(method)) == NULL) {
                AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG, " SSL INIT Failed - Unable to create SSL Context");
                return ResponseCode::NETWORK_SSL_INIT_ERROR;
            }

            return ResponseCode::SUCCESS;
        }
BCryptHashImpl::BCryptHashImpl(LPCWSTR algorithmName, bool isHMAC) :
    m_algorithmHandle(nullptr),
    m_hashBufferLength(0),
    m_hashBuffer(nullptr),
    m_hashObjectLength(0),
    m_hashObject(nullptr),
    m_algorithmMutex()
{
    NTSTATUS status = BCryptOpenAlgorithmProvider(&m_algorithmHandle, algorithmName, MS_PRIMITIVE_PROVIDER, isHMAC ? BCRYPT_ALG_HANDLE_HMAC_FLAG : 0);
    if (!NT_SUCCESS(status))
    {
        AWS_LOG_ERROR(BCRYPT_LOG_TAG, "Failed initializing BCryptOpenAlgorithmProvider for ", algorithmName);
        return;
    }

    DWORD resultLength = 0;
    status = BCryptGetProperty(m_algorithmHandle, BCRYPT_HASH_LENGTH, (PBYTE)&m_hashBufferLength, sizeof(m_hashBufferLength), &resultLength, 0);
    if (!NT_SUCCESS(status) || m_hashBufferLength <= 0)
    {
        AWS_LOG_ERROR(BCRYPT_LOG_TAG, "Error computing hash buffer length.");
        return;
    }

    m_hashBuffer = Aws::NewArray<BYTE>(m_hashBufferLength, BCRYPT_LOG_TAG);
    if (!m_hashBuffer)
    {
        AWS_LOG_ERROR(BCRYPT_LOG_TAG, "Error allocating hash buffer.");
        return;
    }

    resultLength = 0;
    status = BCryptGetProperty(m_algorithmHandle, BCRYPT_OBJECT_LENGTH, (PBYTE)&m_hashObjectLength, sizeof(m_hashObjectLength), &resultLength, 0);
    if (!NT_SUCCESS(status) || m_hashObjectLength <= 0)
    {
        AWS_LOG_ERROR(BCRYPT_LOG_TAG, "Error computing hash object length.");
        return;
    }

    m_hashObject = Aws::NewArray<BYTE>(m_hashObjectLength, BCRYPT_LOG_TAG);
    if (!m_hashObject)
    {
        AWS_LOG_ERROR(BCRYPT_LOG_TAG, "Error allocating hash object.");
        return;
    }
}
HashResult BCryptHashImpl::HashData(const BCryptHashContext& context, PBYTE data, ULONG dataLength)
{
    NTSTATUS status = BCryptHashData(context.m_hashHandle, data, dataLength, 0);
    if (!NT_SUCCESS(status))
    {
        AWS_LOG_ERROR(BCRYPT_LOG_TAG, "Error computing hash.");
        return HashResult();
    }

    status = BCryptFinishHash(context.m_hashHandle, m_hashBuffer, m_hashBufferLength, 0); 
    if (!NT_SUCCESS(status))
    {
        AWS_LOG_ERROR(BCRYPT_LOG_TAG, "Error obtaining computed hash");
        return HashResult();
    }

    return HashResult(ByteBuffer(m_hashBuffer, m_hashBufferLength));
}
bool BCryptHashImpl::HashStream(Aws::IStream& stream)
{
    BCryptHashContext context(m_algorithmHandle, m_hashObject, m_hashObjectLength);
    if (!context.IsValid())
    {
        AWS_LOG_ERROR(BCRYPT_LOG_TAG, "Error creating hash handle.");
        return false;
    }

    char streamBuffer[Aws::Utils::Crypto::Hash::INTERNAL_HASH_STREAM_BUFFER_SIZE];
    NTSTATUS status = 0;
    stream.seekg(0, stream.beg);
    while(stream.good())
    {
        stream.read(streamBuffer, Aws::Utils::Crypto::Hash::INTERNAL_HASH_STREAM_BUFFER_SIZE);
        std::streamsize bytesRead = stream.gcount();
        if(bytesRead > 0)
        {
            status = BCryptHashData(context.m_hashHandle, (PBYTE)streamBuffer, (ULONG) bytesRead, 0);
            if (!NT_SUCCESS(status))
            {
                AWS_LOG_ERROR(BCRYPT_LOG_TAG, "Error computing hash.");
                return false;
            }
        }
    }

    if(!stream.eof())
    {
        return false;
    }

    status = BCryptFinishHash(context.m_hashHandle, m_hashBuffer, m_hashBufferLength, 0); 
    if (!NT_SUCCESS(status))
    {
        AWS_LOG_ERROR(BCRYPT_LOG_TAG, "Error obtaining computed hash");
        return false;
    }

    return true;
}
HashResult BCryptHashImpl::Calculate(const ByteBuffer& toHash, const ByteBuffer& secret)
{
    if(!IsValid())
    {
        return HashResult();
    }

    std::lock_guard<std::mutex> locker(m_algorithmMutex);

    BCryptHashContext context(m_algorithmHandle, m_hashObject, m_hashObjectLength, secret);
    if (!context.IsValid())
    {
        AWS_LOG_ERROR(BCRYPT_LOG_TAG, "Error creating hash handle.");
        return HashResult();
    }

    return HashData(context, static_cast<PBYTE>(toHash.GetUnderlyingData()), static_cast<ULONG>(toHash.GetLength()));
}
HashResult BCryptHashImpl::Calculate(const Aws::String& str)
{
    if(!IsValid())
    {
        return HashResult();
    }

    std::lock_guard<std::mutex> locker(m_algorithmMutex);

    BCryptHashContext context(m_algorithmHandle, m_hashObject, m_hashObjectLength);
    if (!context.IsValid())
    {
        AWS_LOG_ERROR(BCRYPT_LOG_TAG, "Error creating hash handle.");
        return HashResult();
    }

    return HashData(context, (PBYTE)str.c_str(), static_cast<ULONG>(str.length()));
}
        ResponseCode OpenSSLConnection::ConnectInternal() {
            ResponseCode networkResponse = ResponseCode::SUCCESS;

            X509_VERIFY_PARAM *param = nullptr;

            server_tcp_socket_fd_ = socket(AF_INET, SOCK_STREAM, 0);
            if (-1 == server_tcp_socket_fd_) {
                return ResponseCode::NETWORK_TCP_SETUP_ERROR;
            }

            AWS_LOG_DEBUG(OPENSSL_WRAPPER_LOG_TAG, "Root CA : %s", root_ca_location_.c_str());
            if (!SSL_CTX_load_verify_locations(p_ssl_context_, root_ca_location_.c_str(), NULL)) {
                AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG, " Root CA Loading error");
                return ResponseCode::NETWORK_SSL_ROOT_CRT_PARSE_ERROR;
            }

            if (0 < device_cert_location_.length() && 0 < device_private_key_location_.length()) {
                AWS_LOG_DEBUG(OPENSSL_WRAPPER_LOG_TAG, "Device crt : %s", device_cert_location_.c_str());
                if (!SSL_CTX_use_certificate_file(p_ssl_context_, device_cert_location_.c_str(), SSL_FILETYPE_PEM)) {
                    AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG, " Device Certificate Loading error");
                    return ResponseCode::NETWORK_SSL_DEVICE_CRT_PARSE_ERROR;
                }
                AWS_LOG_DEBUG(OPENSSL_WRAPPER_LOG_TAG, "Device privkey : %s", device_private_key_location_.c_str());
                if (1 != SSL_CTX_use_PrivateKey_file(p_ssl_context_,
                                                     device_private_key_location_.c_str(),
                                                     SSL_FILETYPE_PEM)) {
                    AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG, " Device Private Key Loading error");
                    return ResponseCode::NETWORK_SSL_KEY_PARSE_ERROR;
                }
            }

            p_ssl_handle_ = SSL_new(p_ssl_context_);

            // Requires OpenSSL v1.0.2 and above
            if (server_verification_flag_) {
                param = SSL_get0_param(p_ssl_handle_);
                // Enable automatic hostname checks
                X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);

                // Check if it is an IPv4 or an IPv6 address to enable ip checking
                // Enable host name check otherwise
                char dst[INET6_ADDRSTRLEN];
                if (inet_pton(AF_INET, endpoint_.c_str(), (void *) dst) ||
                    inet_pton(AF_INET6, endpoint_.c_str(), (void *) dst)) {
                    X509_VERIFY_PARAM_set1_ip_asc(param, endpoint_.c_str());
                } else {
                    X509_VERIFY_PARAM_set1_host(param, endpoint_.c_str(), 0);
                }
            }

            // Configure a non-zero callback if desired
            SSL_set_verify(p_ssl_handle_, SSL_VERIFY_PEER, nullptr);

            networkResponse = ConnectTCPSocket();
            if (ResponseCode::SUCCESS != networkResponse) {
                AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG, "TCP Connection error");
                return networkResponse;
            }

            SSL_set_fd(p_ssl_handle_, server_tcp_socket_fd_);

            networkResponse = SetSocketToNonBlocking();
            if (ResponseCode::SUCCESS != networkResponse) {
                AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG, " Unable to set the socket to Non-Blocking");
                return networkResponse;
            }

            networkResponse = AttemptConnect();
            if (X509_V_OK != SSL_get_verify_result(p_ssl_handle_)) {
                AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG, " Server Certificate Verification failed.");
                networkResponse = ResponseCode::NETWORK_SSL_CONNECT_ERROR;
            } else {
                // ensure you have a valid certificate returned, otherwise no certificate exchange happened
                if (nullptr == SSL_get_peer_certificate(p_ssl_handle_)) {
                    AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG, " No certificate exchange happened");
                    networkResponse = ResponseCode::NETWORK_SSL_CONNECT_ERROR;
                }
            }

            if (ResponseCode::SUCCESS == networkResponse) {
                is_connected_ = true;
            }

            return networkResponse;
        }