/**
   * Publishes a hidden service
   *
   * @param hiddenServicePort The port that the hidden service will accept connections on
   * @param localPort The local port that the hidden service will relay connections to
   * @return The hidden service's onion address in the form X.onion.
   * @throws java.io.IOException - File errors
   */
  public synchronized String publishHiddenService(int hiddenServicePort, int localPort)
      throws IOException {
    if (controlConnection == null) {
      throw new RuntimeException("Service is not running.");
    }

    List<ConfigEntry> currentHiddenServices = controlConnection.getConf("HiddenServiceOptions");

    if ((currentHiddenServices.size() == 1
            && currentHiddenServices.get(0).key.compareTo("HiddenServiceOptions") == 0
            && currentHiddenServices.get(0).value.compareTo("") == 0)
        == false) {
      throw new RuntimeException(
          "Sorry, only one hidden service to a customer and we already have one. Please send complaints to https://github.com/thaliproject/Tor_Onion_Proxy_Library/issues/5 with your scenario so we can justify fixing this.");
    }

    LOG.info("Creating hidden service");
    File hostnameFile = onionProxyContext.getHostNameFile();

    if (hostnameFile.getParentFile().exists() == false
        && hostnameFile.getParentFile().mkdirs() == false) {
      throw new RuntimeException("Could not create hostnameFile parent directory");
    }

    if (hostnameFile.exists() == false && hostnameFile.createNewFile() == false) {
      throw new RuntimeException("Could not create hostnameFile");
    }

    // Watch for the hostname file being created/updated
    WriteObserver hostNameFileObserver = onionProxyContext.generateWriteObserver(hostnameFile);
    // Use the control connection to update the Tor config
    List<String> config =
        Arrays.asList(
            "HiddenServiceDir " + hostnameFile.getParentFile().getAbsolutePath(),
            "HiddenServicePort " + hiddenServicePort + " 127.0.0.1:" + localPort);
    controlConnection.setConf(config);
    controlConnection.saveConf();
    // Wait for the hostname file to be created/updated
    if (!hostNameFileObserver.poll(HOSTNAME_TIMEOUT, MILLISECONDS)) {
      FileUtilities.listFilesToLog(hostnameFile.getParentFile());
      throw new RuntimeException("Wait for hidden service hostname file to be created expired.");
    }

    // Publish the hidden service's onion hostname in transport properties
    String hostname = new String(FileUtilities.read(hostnameFile), "UTF-8").trim();
    LOG.info("Hidden service config has completed.");

    return hostname;
  }
  protected synchronized void installAndConfigureFiles() throws IOException, InterruptedException {
    onionProxyContext.installFiles();

    if (!setExecutable(onionProxyContext.getTorExecutableFile())) {
      throw new RuntimeException("could not make Tor executable.");
    }

    // We need to edit the config file to specify exactly where the cookie/geoip files should be
    // stored, on
    // Android this is always a fixed location relative to the configFiles which is why this extra
    // step
    // wasn't needed in Briar's Android code. But in Windows it ends up in the user's
    // AppData/Roaming. Rather
    // than track it down we just tell Tor where to put it.
    PrintWriter printWriter = null;
    try {
      printWriter =
          new PrintWriter(
              new BufferedWriter(new FileWriter(onionProxyContext.getTorrcFile(), true)));
      printWriter.println("CookieAuthFile " + onionProxyContext.getCookieFile().getAbsolutePath());
      // For some reason the GeoIP's location can only be given as a file name, not a path and it
      // has
      // to be in the data directory so we need to set both
      printWriter.println(
          "DataDirectory " + onionProxyContext.getWorkingDirectory().getAbsolutePath());
      printWriter.println("GeoIPFile " + onionProxyContext.getGeoIpFile().getName());
      printWriter.println("GeoIPv6File " + onionProxyContext.getGeoIpv6File().getName());
    } finally {
      if (printWriter != null) {
        printWriter.close();
      }
    }
  }
  /**
   * This is a blocking call that will try to start the Tor OP, connect it to the network and get it
   * to be fully bootstrapped. Sometimes the bootstrap process just hangs for no apparent reason so
   * the method will wait for the given time for bootstrap to finish and if it doesn't then will
   * restart the bootstrap process the given number of repeats.
   *
   * @param secondsBeforeTimeOut Seconds to wait for boot strapping to finish
   * @param numberOfRetries Number of times to try recycling the Tor OP before giving up on
   *     bootstrapping working
   * @return True if bootstrap succeeded, false if there is a problem or the bootstrap couldn't
   *     complete in the given time.
   * @throws java.lang.InterruptedException - You know, if we are interrupted
   * @throws java.io.IOException - IO Exceptions
   */
  public synchronized boolean startWithRepeat(int secondsBeforeTimeOut, int numberOfRetries)
      throws InterruptedException, IOException {
    if (secondsBeforeTimeOut <= 0 || numberOfRetries < 0) {
      throw new IllegalArgumentException("secondsBeforeTimeOut >= 0 & numberOfRetries > 0");
    }

    try {
      for (int retryCount = 0; retryCount < numberOfRetries; ++retryCount) {
        if (installAndStartTorOp() == false) {
          return false;
        }
        enableNetwork(true);

        // We will check every second to see if boot strapping has finally finished
        for (int secondsWaited = 0; secondsWaited < secondsBeforeTimeOut; ++secondsWaited) {
          if (isBootstrapped() == false) {
            Thread.sleep(1000, 0);
          } else {
            return true;
          }
        }

        // Bootstrapping isn't over so we need to restart and try again
        stop();
        // Experimentally we have found that if a Tor OP has run before and thus has cached
        // descriptors
        // and that when we try to start it again it won't start then deleting the cached data can
        // fix this.
        // But, if there is cached data and things do work then the Tor OP will start faster than it
        // would
        // if we delete everything.
        // So our compromise is that we try to start the Tor OP 'as is' on the first round and after
        // that
        // we delete all the files.
        onionProxyContext.deleteAllFilesButHiddenServices();
      }

      return false;
    } finally {
      // Make sure we return the Tor OP in some kind of consistent state, even if it's 'off'.
      if (isRunning() == false) {
        stop();
      }
    }
  }
 /**
  * Returns the root directory in which the Tor Onion Proxy keeps its files. This is mostly
  * intended for debugging purposes.
  *
  * @return Working directory for Tor Onion Proxy files
  */
 public File getWorkingDirectory() {
   return onionProxyContext.getWorkingDirectory();
 }
  /**
   * Installs all necessary files and starts the Tor OP in offline mode (e.g.
   * networkEnabled(false)). This would only be used if you wanted to start the Tor OP so that the
   * install and related is all done but aren't ready to actually connect it to the network.
   *
   * @return True if all files installed and Tor OP successfully started
   * @throws java.io.IOException - IO Exceptions
   * @throws java.lang.InterruptedException - If we are, well, interrupted
   */
  public synchronized boolean installAndStartTorOp() throws IOException, InterruptedException {
    // The Tor OP will die if it looses the connection to its socket so if there is no controlSocket
    // defined
    // then Tor is dead. This assumes, of course, that takeOwnership works and we can't end up with
    // Zombies.
    if (controlConnection != null) {
      LOG.info("Tor is already running");
      return true;
    }

    // The code below is why this method is synchronized, we don't want two instances of it running
    // at once
    // as the result would be a mess of screwed up files and connections.
    LOG.info("Tor is not running");

    installAndConfigureFiles();

    LOG.info("Starting Tor");
    File cookieFile = onionProxyContext.getCookieFile();
    if (cookieFile.getParentFile().exists() == false
        && cookieFile.getParentFile().mkdirs() == false) {
      throw new RuntimeException("Could not create cookieFile parent directory");
    }

    // The original code from Briar watches individual files, not a directory and Android's file
    // observer
    // won't work on files that don't exist. Rather than take 5 seconds to rewrite Briar's code I
    // instead
    // just make sure the file exists
    if (cookieFile.exists() == false && cookieFile.createNewFile() == false) {
      throw new RuntimeException("Could not create cookieFile");
    }

    File workingDirectory = onionProxyContext.getWorkingDirectory();
    // Watch for the auth cookie file being created/updated
    WriteObserver cookieObserver = onionProxyContext.generateWriteObserver(cookieFile);
    // Start a new Tor process
    String torPath = onionProxyContext.getTorExecutableFile().getAbsolutePath();
    String configPath = onionProxyContext.getTorrcFile().getAbsolutePath();
    String pid = onionProxyContext.getProcessId();
    String[] cmd = {torPath, "-f", configPath, OWNER, pid};
    String[] env = onionProxyContext.getEnvironmentArgsForExec();
    ProcessBuilder processBuilder = new ProcessBuilder(cmd);
    onionProxyContext.setEnvironmentArgsAndWorkingDirectoryForStart(processBuilder);
    Process torProcess = null;
    try {
      //            torProcess = Runtime.getRuntime().exec(cmd, env, workingDirectory);
      torProcess = processBuilder.start();
      CountDownLatch controlPortCountDownLatch = new CountDownLatch(1);
      eatStream(torProcess.getInputStream(), false, controlPortCountDownLatch);
      eatStream(torProcess.getErrorStream(), true, null);

      // On platforms other than Windows we run as a daemon and so we need to wait for the process
      // to detach
      // or exit. In the case of Windows the equivalent is running as a service and unfortunately
      // that requires
      // managing the service, such as turning it off or uninstalling it when it's time to move on.
      // Any number
      // of errors can prevent us from doing the cleanup and so we would leave the process running
      // around. Rather
      // than do that on Windows we just let the process run on the exec and hence don't look for an
      // exit code.
      // This does create a condition where the process has exited due to a problem but we should
      // hopefully
      // detect that when we try to use the control connection.
      if (OsData.getOsType() != OsData.OsType.Windows) {
        int exit = torProcess.waitFor();
        torProcess = null;
        if (exit != 0) {
          LOG.warn("Tor exited with value " + exit);
          return false;
        }
      }

      // Wait for the auth cookie file to be created/updated
      if (!cookieObserver.poll(COOKIE_TIMEOUT, MILLISECONDS)) {
        LOG.warn("Auth cookie not created");
        FileUtilities.listFilesToLog(workingDirectory);
        return false;
      }

      // Now we should be able to connect to the new process
      controlPortCountDownLatch.await();
      controlSocket = new Socket("127.0.0.1", control_port);

      // Open a control connection and authenticate using the cookie file
      TorControlConnection controlConnection = new TorControlConnection(controlSocket);
      controlConnection.authenticate(FileUtilities.read(cookieFile));
      // Tell Tor to exit when the control connection is closed
      controlConnection.takeOwnership();
      controlConnection.resetConf(Collections.singletonList(OWNER));
      // Register to receive events from the Tor process
      controlConnection.setEventHandler(new OnionProxyManagerEventHandler());
      controlConnection.setEvents(Arrays.asList(EVENTS));

      // We only set the class property once the connection is in a known good state
      this.controlConnection = controlConnection;
      return true;
    } catch (SecurityException e) {
      LOG.warn(e.toString(), e);
      return false;
    } catch (InterruptedException e) {
      LOG.warn("Interrupted while starting Tor", e);
      Thread.currentThread().interrupt();
      return false;
    } finally {
      if (controlConnection == null && torProcess != null) {
        // It's possible that something 'bad' could happen after we executed exec but before we
        // takeOwnership()
        // in which case the Tor OP will hang out as a zombie until this process is killed. This is
        // problematic
        // when we want to do things like
        torProcess.destroy();
      }
    }
  }