class Seahorse::Client::H2::Connection

Ruby >= 2.3 and OpenSSL >= 1.0.2
with TLS layer plus ALPN, requires:
(requires Ruby >= 2.1)
H2 Connection build on top of ‘http/2` gem

def _debug_entry(str)

def _debug_entry(str)
  @logger << str
  @logger << "\n"
end

def _default_ca_bundle

def _default_ca_bundle
  File.exist?(OpenSSL::X509::DEFAULT_CERT_FILE) ?
    OpenSSL::X509::DEFAULT_CERT_FILE : nil
end

def _default_ca_directory

def _default_ca_directory
  Dir.exist?(OpenSSL::X509::DEFAULT_CERT_DIR) ?
    OpenSSL::X509::DEFAULT_CERT_DIR : nil
end

def _nonblocking_connect(tcp, addr)

def _nonblocking_connect(tcp, addr)
  begin
    tcp.connect_nonblock(addr)
  rescue IO::WaitWritable
    unless IO.select(nil, [tcp], nil, connection_timeout)
      tcp.close
      raise
    end
    begin
      tcp.connect_nonblock(addr)
    rescue Errno::EISCONN
      # tcp socket connected, continue
    end
  end
end

def _register_h2_callbacks

def _register_h2_callbacks
  @h2_client.on(:frame) do |bytes|
    if @socket.nil?
      msg = 'Connection is closed due to errors, '\
            'you can find errors at async_client.connection.errors'
      raise Http2ConnectionClosedError.new(msg)
    else
      @socket.print(bytes)
      @socket.flush
    end
  end
  if @http_wire_trace
    @h2_client.on(:frame_sent) do |frame|
      debug_output("frame: #{frame.inspect}", :send)
    end
    @h2_client.on(:frame_received) do |frame|
      debug_output("frame: #{frame.inspect}", :receive)
    end
  end
end

def _tcp_socket(endpoint)

def _tcp_socket(endpoint)
  tcp = ::Socket.new(SOCKET_FAMILY, ::Socket::SOCK_STREAM, 0)
  tcp.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
  address = ::Socket.getaddrinfo(endpoint.host, nil, SOCKET_FAMILY).first[3]
  sockaddr = ::Socket.sockaddr_in(endpoint.port, address)
  [tcp, sockaddr]
end

def _tls_context

def _tls_context
  ssl_ctx = OpenSSL::SSL::SSLContext.new(:TLSv1_2)
  if ssl_verify_peer?
    ssl_ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
    ssl_ctx.ca_file = ssl_ca_bundle ? ssl_ca_bundle : _default_ca_bundle
    ssl_ctx.ca_path = ssl_ca_directory ? ssl_ca_directory : _default_ca_directory
    ssl_ctx.cert_store = ssl_ca_store if ssl_ca_store
  else
    ssl_ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
  end
  if enable_alpn
    debug_output('enabling ALPN for TLS ...')
    ssl_ctx.alpn_protocols = ['h2']
  end
  ssl_ctx
end

def close!

def close!
  @mutex.synchronize {
    self.debug_output('closing connection ...')
    if @socket
      @socket.close
      @socket = nil
    end
    @status = :closed
  }
end

def closed?

def closed?
  @status == :closed
end

def connect(endpoint)

def connect(endpoint)
  @mutex.synchronize {
    if @status == :ready
      tcp, addr = _tcp_socket(endpoint)
      debug_output("opening connection to #{endpoint.host}:#{endpoint.port} ...")
      _nonblocking_connect(tcp, addr)
      debug_output('opened')
      if endpoint.scheme == 'https'
        @socket = OpenSSL::SSL::SSLSocket.new(tcp, _tls_context)
        @socket.sync_close = true
        @socket.hostname = endpoint.host
        debug_output("starting TLS for #{endpoint.host}:#{endpoint.port} ...")
        @socket.connect
        debug_output('TLS established')
      else
        @socket = tcp
      end
      _register_h2_callbacks
      @status = :active
    elsif @status == :closed
      msg = 'Async Client HTTP2 Connection is closed, you may'\
            ' use #new_connection to create a new HTTP2 Connection for this client'
      raise Http2ConnectionClosedError.new(msg)
    end
  }
end

def debug_output(msg, type = nil)

def debug_output(msg, type = nil)
  prefix = case type
    when :send then '-> '
    when :receive then '<- '
    else
      ''
    end
  return unless @logger
  _debug_entry(prefix + msg)
end

def initialize(options = {})

def initialize(options = {})
  OPTIONS.each_pair do |opt_name, default_value|
    value = options[opt_name].nil? ? default_value : options[opt_name]
    instance_variable_set("@#{opt_name}", value)
  end
  @h2_client = HTTP2::Client.new(
    settings_max_concurrent_streams: max_concurrent_streams
  )
  @logger = if @http_wire_trace
    options[:logger] || Logger.new($stdout)
  end
  @chunk_size = options[:read_chunk_size] || CHUNKSIZE
  @errors = []
  @status = :ready
  @mutex = Mutex.new # connection can be shared across requests
  @socket = nil
  @socket_thread = nil
end

def new_stream

def new_stream
  begin
    @h2_client.new_stream
  rescue => error
    raise Http2StreamInitializeError.new(error)
  end
end

def start(stream)

def start(stream)
  @mutex.synchronize {
    return if @socket_thread
    @socket_thread = Thread.new do
      while @socket && !@socket.closed?
        begin
          data = @socket.read_nonblock(@chunk_size)
          @h2_client << data
        rescue IO::WaitReadable
          begin
            unless IO.select([@socket], nil, nil, connection_read_timeout)
              self.debug_output('socket connection read time out')
              self.close!
            else
              # available, retry to start reading
              retry
            end
          rescue
            # error can happen when closing the socket
            # while it's waiting for read
            self.close!
          end
        rescue EOFError
          self.close!
        rescue => error
          self.debug_output(error.inspect)
          @errors << error
          self.close!
        end
      end
      @socket_thread = nil
    end
    @socket_thread.abort_on_exception = true
  }
end