/**
   * @param address The request bind address
   * @param handlerFactory A {@link Factory} to create an {@link IoHandler} if necessary
   * @return The {@link InetSocketAddress} to which the binding occurred
   * @throws IOException If failed to bind
   */
  private InetSocketAddress doBind(
      SshdSocketAddress address, Factory<? extends IoHandler> handlerFactory) throws IOException {
    if (acceptor == null) {
      FactoryManager manager = session.getFactoryManager();
      IoServiceFactory factory = manager.getIoServiceFactory();
      IoHandler handler = handlerFactory.create();
      acceptor = factory.createAcceptor(handler);
    }

    // TODO find a better way to determine the resulting bind address - what if multi-threaded
    // calls...
    Set<SocketAddress> before = acceptor.getBoundAddresses();
    try {
      InetSocketAddress bindAddress = address.toInetSocketAddress();
      acceptor.bind(bindAddress);

      Set<SocketAddress> after = acceptor.getBoundAddresses();
      if (GenericUtils.size(after) > 0) {
        after.removeAll(before);
      }
      if (GenericUtils.isEmpty(after)) {
        throw new IOException(
            "Error binding to " + address + "[" + bindAddress + "]: no local addresses bound");
      }

      if (after.size() > 1) {
        throw new IOException(
            "Multiple local addresses have been bound for " + address + "[" + bindAddress + "]");
      }
      return (InetSocketAddress) after.iterator().next();
    } catch (IOException bindErr) {
      Set<SocketAddress> after = acceptor.getBoundAddresses();
      if (GenericUtils.isEmpty(after)) {
        close();
      }
      throw bindErr;
    }
  }
  @Override
  public synchronized void stopDynamicPortForwarding(SshdSocketAddress local) throws IOException {
    Closeable obj;
    synchronized (dynamicLocal) {
      obj = dynamicLocal.remove(local.getPort());
    }

    if (obj != null) {
      if (log.isDebugEnabled()) {
        log.debug("stopDynamicPortForwarding(" + local + ") unbinding");
      }
      obj.close(true);
      acceptor.unbind(local.toInetSocketAddress());
    } else {
      if (log.isDebugEnabled()) {
        log.debug("stopDynamicPortForwarding(" + local + ") no binding found");
      }
    }
  }
  @Override
  public synchronized void stopLocalPortForwarding(SshdSocketAddress local) throws IOException {
    ValidateUtils.checkNotNull(local, "Local address is null");

    SshdSocketAddress bound;
    synchronized (localToRemote) {
      bound = localToRemote.remove(local.getPort());
    }

    if ((bound != null) && (acceptor != null)) {
      if (log.isDebugEnabled()) {
        log.debug("stopLocalPortForwarding(" + local + ") unbind " + bound);
      }
      acceptor.unbind(bound.toInetSocketAddress());
    } else {
      if (log.isDebugEnabled()) {
        log.debug("stopLocalPortForwarding(" + local + ") no mapping/acceptor for " + bound);
      }
    }
  }
  @Override
  public synchronized void localPortForwardingCancelled(SshdSocketAddress local)
      throws IOException {
    LocalForwardingEntry entry;
    synchronized (localForwards) {
      entry =
          LocalForwardingEntry.findMatchingEntry(
              local.getHostName(), local.getPort(), localForwards);
      if (entry != null) {
        localForwards.remove(entry);
      }
    }

    if ((entry != null) && (acceptor != null)) {
      if (log.isDebugEnabled()) {
        log.debug("localPortForwardingCancelled(" + local + ") unbind " + entry);
      }
      acceptor.unbind(entry.toInetSocketAddress());
    } else {
      if (log.isDebugEnabled()) {
        log.debug("localPortForwardingCancelled(" + local + ") no match/acceptor: " + entry);
      }
    }
  }