/** Accept a connection from a debuggee and handshake with it. */
  public Connection accept(ListenKey listener, long acceptTimeout, long handshakeTimeout)
      throws IOException {
    if (acceptTimeout < 0 || handshakeTimeout < 0) {
      throw new IllegalArgumentException("timeout is negative");
    }
    if (!(listener instanceof SocketListenKey)) {
      throw new IllegalArgumentException("Invalid listener");
    }
    ServerSocket ss;

    // obtain the ServerSocket from the listener - if the
    // socket is closed it means the listener is invalid
    synchronized (listener) {
      ss = ((SocketListenKey) listener).socket();
      if (ss.isClosed()) {
        throw new IllegalArgumentException("Invalid listener");
      }
    }

    // from here onwards it's possible that the ServerSocket
    // may be closed by a call to stopListening - that's okay
    // because the ServerSocket methods will throw an
    // IOException indicating the socket is closed.
    //
    // Additionally, it's possible that another thread calls accept
    // with a different accept timeout - that creates a same race
    // condition between setting the timeout and calling accept.
    // As it is such an unlikely scenario (requires both threads
    // to be using the same listener we've chosen to ignore the issue).

    ss.setSoTimeout((int) acceptTimeout);
    Socket s;
    try {
      s = ss.accept();
    } catch (SocketTimeoutException x) {
      throw new TransportTimeoutException("timeout waiting for connection");
    }

    // handshake here
    handshake(s, handshakeTimeout);

    return new SocketConnection(s);
  }