class HTTPX::TLS::Context

def self.build_alpn_string(protos)

def self.build_alpn_string(protos)
  protos.reduce("".b) do |buffer, proto|
    buffer << proto.bytesize
    buffer << proto
  end
end

def add_server_name_indication

def add_server_name_indication
  raise Error, "only valid for server mode context" unless @is_server
  SSL.SSL_CTX_set_tlsext_servername_callback(@ssl_ctx, ServerNameCB)
end

def cleanup

def cleanup
  return unless @ssl_ctx
  SSL.SSL_CTX_free(@ssl_ctx)
  @ssl_ctx = nil
end

def initialize(server, options = {})

def initialize(server, options = {})
  @is_server = server
  if @is_server
    @ssl_ctx = SSL.SSL_CTX_new(SSL.TLS_server_method)
    set_private_key(options[:private_key] || SSL::DEFAULT_PRIVATE)
    set_certificate(options[:cert_chain]  || SSL::DEFAULT_CERT)
    set_client_ca(options[:client_ca])
  else
    @ssl_ctx = SSL.SSL_CTX_new(SSL.TLS_client_method)
  end
  SSL.SSL_CTX_set_options(@ssl_ctx, SSL::SSL_OP_ALL)
  SSL.SSL_CTX_set_mode(@ssl_ctx, SSL::SSL_MODE_RELEASE_BUFFERS)
  SSL.SSL_CTX_set_cipher_list(@ssl_ctx, options[:ciphers] || CIPHERS)
  set_min_version(options[:version])
  if @is_server
    SSL.SSL_CTX_sess_set_cache_size(@ssl_ctx, 128)
    SSL.SSL_CTX_set_session_id_context(@ssl_ctx, SESSION, 8)
  else
    set_private_key(options[:private_key])
    set_certificate(options[:cert_chain])
  end
  set_alpn_negotiation(options[:protocols])
end

def set_alpn_negotiation(protocols)

def set_alpn_negotiation(protocols)
  @alpn_set = false
  return unless protocols
  if @is_server
    @alpn_str = Context.build_alpn_string(protocols)
    SSL.SSL_CTX_set_alpn_select_cb(@ssl_ctx, ALPN_Select_CB, nil)
    @alpn_set = true
  else
    protocols = Context.build_alpn_string(protocols)
    @alpn_set = SSL.SSL_CTX_set_alpn_protos(@ssl_ctx, protocols, protocols.length) == 0
  end
end

def set_alpn_negotiation(_protocols); end

def set_alpn_negotiation(_protocols); end

def set_certificate(cert)

def set_certificate(cert)
  err = if cert.is_a? FFI::Pointer
    SSL.SSL_CTX_use_certificate(@ssl_ctx, cert)
  elsif cert && File.file?(cert)
    SSL.SSL_CTX_use_certificate_chain_file(@ssl_ctx, cert)
  else
    1
  end
  if err <= 0
    cleanup
    raise Error, "invalid certificate or file not found"
  end
end

def set_client_ca(ca)

def set_client_ca(ca)
  return unless ca
  if File.file?(ca) && (ca_ptr = SSL.SSL_load_client_CA_file(ca))
    # there is no error checking provided by SSL_CTX_set_client_CA_list
    SSL.SSL_CTX_set_client_CA_list(@ssl_ctx, ca_ptr)
  else
    cleanup
    raise Error, "invalid ca certificate or file not found"
  end
end

def set_min_version(version)

def set_min_version(version)
  return unless version
  num = SSL.const_get("#{version}_VERSION")
  SSL.SSL_CTX_set_min_proto_version(@ssl_ctx, num) == 1
rescue NameError
  raise Error, "#{version} is unsupported"
end

def set_min_version(_version); end

def set_min_version(_version); end

def set_private_key(key)

def set_private_key(key)
  err = if key.is_a? FFI::Pointer
    SSL.SSL_CTX_use_PrivateKey(@ssl_ctx, key)
  elsif key && File.file?(key)
    SSL.SSL_CTX_use_PrivateKey_file(@ssl_ctx, key, SSL_FILETYPE_PEM)
  else
    1
  end
  # Check for errors
  if err <= 0
    # TODO: : ERR_print_errors_fp or ERR_print_errors
    # So we can properly log the issue
    cleanup
    raise Error, "invalid private key or file not found"
  end
end