/**
   * Accepts the SOCKS5 Bytestream initialization request and returns the socket to send/receive
   * data.
   *
   * <p>Before accepting the SOCKS5 Bytestream request you can set timeouts by invoking {@link
   * #setTotalConnectTimeout(int)} and {@link #setMinimumConnectTimeout(int)}.
   *
   * @return the socket to send/receive data
   * @throws XMPPException if connection to all SOCKS5 proxies failed or if stream is invalid.
   * @throws InterruptedException if the current thread was interrupted while waiting
   */
  public Socks5BytestreamSession accept() throws XMPPException, InterruptedException {
    Collection<StreamHost> streamHosts = this.bytestreamRequest.getStreamHosts();

    // throw exceptions if request contains no stream hosts
    if (streamHosts.size() == 0) {
      cancelRequest();
    }

    StreamHost selectedHost = null;
    Socket socket = null;

    String digest =
        Socks5Utils.createDigest(
            this.bytestreamRequest.getSessionID(),
            this.bytestreamRequest.getFrom(),
            this.manager.getConnection().getUser());

    /*
     * determine timeout for each connection attempt; each SOCKS5 proxy has the same amount of
     * time so that the first does not consume the whole timeout
     */
    int timeout =
        Math.max(getTotalConnectTimeout() / streamHosts.size(), getMinimumConnectTimeout());

    for (StreamHost streamHost : streamHosts) {
      String address = streamHost.getAddress() + ":" + streamHost.getPort();

      // check to see if this address has been blacklisted
      int failures = getConnectionFailures(address);
      if (CONNECTION_FAILURE_THRESHOLD > 0 && failures >= CONNECTION_FAILURE_THRESHOLD) {
        continue;
      }

      // establish socket
      try {

        // build SOCKS5 client
        final Socks5Client socks5Client = new Socks5Client(streamHost, digest);

        // connect to SOCKS5 proxy with a timeout
        socket = socks5Client.getSocket(timeout);

        // set selected host
        selectedHost = streamHost;
        break;

      } catch (TimeoutException e) {
        incrementConnectionFailures(address);
      } catch (IOException e) {
        incrementConnectionFailures(address);
      } catch (XMPPException e) {
        incrementConnectionFailures(address);
      }
    }

    // throw exception if connecting to all SOCKS5 proxies failed
    if (selectedHost == null || socket == null) {
      cancelRequest();
    }

    // send used-host confirmation
    Bytestream response = createUsedHostResponse(selectedHost);
    this.manager.getConnection().sendPacket(response);

    return new Socks5BytestreamSession(
        socket, selectedHost.getJID().equals(this.bytestreamRequest.getFrom()));
  }