/**
   * Perform the actual version check by connecting to the version server.
   *
   * @param data_to_send version message
   * @return version reply
   * @throws Exception if the server check connection fails
   */
  private Map performVersionCheck(
      Map data_to_send, boolean use_az_message, boolean use_http, boolean v6) throws Exception {
    Exception error = null;
    Map reply = null;

    if (use_http) {

      try {
        reply = executeHTTP(data_to_send, v6);

        reply.put("protocol_used", "HTTP");

        error = null;
      } catch (IOException e) {
        error = e;
      } catch (Exception e) {
        Debug.printStackTrace(e);
        error = e;
      }
    }

    if (reply == null && use_az_message) {

      try {
        reply = executeAZMessage(data_to_send, v6);

        reply.put("protocol_used", "AZMSG");
      } catch (IOException e) {
        error = e;
      } catch (Exception e) {
        Debug.printStackTrace(e);
        error = e;
      }
    }
    if (error != null) {

      throw (error);
    }

    if (Logger.isEnabled())
      Logger.log(
          new LogEvent(
              LOGID,
              "VersionCheckClient server "
                  + "version check successful. Received "
                  + reply.size()
                  + " reply keys."));

    if (v6) {

      last_check_time_v6 = SystemTime.getCurrentTime();

    } else {

      last_check_time_v4 = SystemTime.getCurrentTime();
    }

    return reply;
  }
  public InetAddress getExternalIpAddressUDP(InetAddress bind_ip, int bind_port, boolean v6)
      throws Exception {
    Map reply = executeUDP(new HashMap(), bind_ip, bind_port, v6);

    byte[] address = (byte[]) reply.get("source_ip_address");

    return (InetAddress.getByName(new String(address)));
  }
  public InetAddress getExternalIpAddressHTTP(boolean v6) throws Exception {

    Map reply = executeHTTP(new HashMap(), v6);

    byte[] address = (byte[]) reply.get("source_ip_address");

    return (InetAddress.getByName(new String(address)));
  }
  public Set<String> getAutoInstallPluginIDs() {
    Set<String> result = new HashSet<String>();

    Map m = getMostRecentVersionCheckData();

    if (m != null) {

      byte[] x = (byte[]) m.get("autoinstall_pids");

      if (x != null) {

        String str = new String(x);

        String latest = COConfigurationManager.getStringParameter("vc.autoinstall_pids.latest", "");

        if (!str.equals(latest)) {

          byte[] sig = (byte[]) m.get("autoinstall_pids_sig");

          if (sig == null) {

            Debug.out("autoinstall plugins sig missing");

            return (result);
          }

          try {
            AEVerifier.verifyData(str, sig);

            COConfigurationManager.setParameter("vc.autoinstall_pids.latest", str);

          } catch (Throwable e) {

            return (result);
          }
        }

        String[] bits = str.split(",");

        for (String b : bits) {

          b = b.trim();

          if (b.length() > 0) {

            result.add(b);
          }
        }
      }
    }

    return (result);
  }
  public Map<String, Object> getCountryInfo() {
    Map reply = getVersionCheckInfo(REASON_EXTERNAL_IP, AT_EITHER);

    Map<String, Object> info = (Map<String, Object>) reply.get("source_info");

    if (info == null) {

      return (new HashMap<String, Object>());

    } else {

      return (BDecoder.decodeStrings(info));
    }
  }
  private boolean isVersionCheckDataValid(int address_type) {
    boolean v6_ok = last_check_data_v6 != null && last_check_data_v6.size() > 0;
    boolean v4_ok = last_check_data_v4 != null && last_check_data_v4.size() > 0;

    if (address_type == AT_V4) {

      return (v4_ok);

    } else if (address_type == AT_V6) {

      return (v6_ok);

    } else {

      return (v4_ok | v6_ok);
    }
  }
  /**
   * Is the DHT allowed to be used by external plugins.
   *
   * @return true if extended DHT use is allowed, false if not allowed
   */
  public boolean DHTExtendedUseAllowed() {
    Map reply = getVersionCheckInfo(REASON_DHT_EXTENDED_ALLOWED, AT_EITHER);

    boolean res = false;

    byte[] value = (byte[]) reply.get("enable_dht_extended_use");
    if (value != null) {
      res = new String(value).equalsIgnoreCase("true");
    }

    // be generous and enable extended use if check failed

    if (!res) {
      res = !isVersionCheckDataValid(AT_EITHER);
    }

    return res;
  }
  public String[] getRecommendedPlugins() {
    Map reply = getVersionCheckInfo(REASON_RECOMMENDED_PLUGINS, AT_EITHER);

    List l = (List) reply.get("recommended_plugins");

    if (l == null) {

      return (new String[0]);
    }

    String[] res = new String[l.size()];

    for (int i = 0; i < l.size(); i++) {

      res[i] = new String((byte[]) l.get(i));
    }

    return (res);
  }
  public long getFeatureFlags() {
    long now = SystemTime.getCurrentTime();

    if (now > last_feature_flag_cache_time && now - last_feature_flag_cache_time < 60000) {

      return (last_feature_flag_cache);
    }

    Map m = getMostRecentVersionCheckData();

    long result;

    if (m == null) {

      result = 0;

    } else {

      byte[] b_feat_flags = (byte[]) m.get("feat_flags");

      if (b_feat_flags != null) {

        try {

          result = Long.parseLong(new String((byte[]) b_feat_flags));

        } catch (Throwable e) {

          result = 0;
        }
      } else {

        result = 0;
      }
    }

    last_feature_flag_cache = result;
    last_feature_flag_cache_time = now;

    return (result);
  }
  /**
   * Is the DHT plugin allowed to be enabled.
   *
   * @return true if DHT can be enabled, false if it should not be enabled
   */
  public boolean DHTEnableAllowed() {
    Map reply = getVersionCheckInfo(REASON_DHT_ENABLE_ALLOWED, AT_EITHER);

    boolean res = false;

    byte[] value = (byte[]) reply.get("enable_dht");

    if (value != null) {

      res = new String(value).equalsIgnoreCase("true");
    }

    // we take the view that if the version check failed then we go ahead
    // and enable the DHT (i.e. we're being optimistic)

    if (!res) {
      res = !isVersionCheckDataValid(AT_EITHER);
    }

    return res;
  }
  public Map getVersionCheckInfo(String reason, int address_type) {
    if (address_type == AT_V4) {

      return (getVersionCheckInfoSupport(reason, false, false, false));

    } else if (address_type == AT_V6) {

      return (getVersionCheckInfoSupport(reason, false, false, true));

    } else {

      Map reply = getVersionCheckInfoSupport(reason, false, false, prefer_v6);

      if (reply == null || reply.size() == 0) {

        reply = getVersionCheckInfoSupport(reason, false, false, !prefer_v6);
      }

      return (reply);
    }
  }
  // Used for debugging in main.
  private static void printDataMap(Map map) throws Exception {
    TreeMap res = new TreeMap(map);
    Iterator key_itr = map.keySet().iterator();
    while (key_itr.hasNext()) {
      Object key = key_itr.next();
      Object val = map.get(key);
      if (val instanceof byte[]) {
        String as_bytes = ByteFormatter.nicePrint((byte[]) val);
        String as_text = new String((byte[]) val, Constants.BYTE_ENCODING);
        res.put(key, as_text + " [" + as_bytes + "]");
      }
    }

    Iterator entries = res.entrySet().iterator();
    Map.Entry entry;
    while (entries.hasNext()) {
      entry = (Map.Entry) entries.next();
      System.out.print("  ");
      System.out.print(entry.getKey());
      System.out.print(": ");
      System.out.print(entry.getValue());
      System.out.println();
    }
  }
  protected Map getVersionCheckInfoSupport(
      String reason, boolean only_if_cached, boolean force, boolean v6) {
    try {
      synchronized (listeners) {
        if (REASON_UPDATE_CHECK_START.equals(reason)) {
          startCheckRan = true;
        }
        for (VersionCheckClientListener l : listeners) {
          l.versionCheckStarted(reason);
        }
      }
    } catch (Throwable t) {
      Debug.out(t);
    }
    if (v6) {

      if (enable_v6) {

        try {
          check_mon.enter();

          long time_diff = SystemTime.getCurrentTime() - last_check_time_v6;

          force = force || time_diff > CACHE_PERIOD || time_diff < 0;

          if (last_check_data_v6 == null || last_check_data_v6.size() == 0 || force) {
            // if we've never checked before then we go ahead even if the "only_if_cached"
            // flag is set as its had not chance of being cached yet!
            if (only_if_cached && last_check_data_v6 != null) {
              return (new HashMap());
            }
            try {
              last_check_data_v6 =
                  performVersionCheck(constructVersionCheckMessage(reason), true, true, true);

              if (last_check_data_v6 != null && last_check_data_v6.size() > 0) {

                COConfigurationManager.setParameter("versioncheck.cache.v6", last_check_data_v6);
              }
            } catch (SocketException t) {
              // internet is broken
              // Debug.out(t.getClass().getName() + ": " + t.getMessage());
            } catch (UnknownHostException t) {
              // dns is broken
              // Debug.out(t.getClass().getName() + ": " + t.getMessage());
            } catch (Throwable t) {
              Debug.out(t);
              last_check_data_v6 = new HashMap();
            }
          } else {
            Logger.log(
                new LogEvent(
                    LOGID,
                    "VersionCheckClient is using "
                        + "cached version check info. Using "
                        + last_check_data_v6.size()
                        + " reply keys."));
          }
        } finally {
          check_mon.exit();
        }
      }

      if (last_check_data_v6 == null) last_check_data_v6 = new HashMap();

      return last_check_data_v6;

    } else {

      try {
        check_mon.enter();

        long time_diff = SystemTime.getCurrentTime() - last_check_time_v4;

        force = force || time_diff > CACHE_PERIOD || time_diff < 0;

        if (last_check_data_v4 == null || last_check_data_v4.size() == 0 || force) {
          // if we've never checked before then we go ahead even if the "only_if_cached"
          // flag is set as its had not chance of being cached yet!
          if (only_if_cached && last_check_data_v4 != null) {
            return (new HashMap());
          }
          try {
            last_check_data_v4 =
                performVersionCheck(constructVersionCheckMessage(reason), true, true, false);

            if (last_check_data_v4 != null && last_check_data_v4.size() > 0) {

              COConfigurationManager.setParameter("versioncheck.cache.v4", last_check_data_v4);
            }

            // clear down any plugin-specific data that has successfully been sent to the version
            // server

            try {
              if (AzureusCoreFactory.isCoreAvailable()) {

                // installed plugin IDs
                PluginInterface[] plugins =
                    AzureusCoreFactory.getSingleton().getPluginManager().getPluginInterfaces();

                for (int i = 0; i < plugins.length; i++) {

                  PluginInterface plugin = plugins[i];

                  Map data =
                      plugin
                          .getPluginconfig()
                          .getPluginMapParameter("plugin.versionserver.data", null);

                  if (data != null) {

                    plugin
                        .getPluginconfig()
                        .setPluginMapParameter("plugin.versionserver.data", new HashMap());
                  }
                }
              }
            } catch (Throwable e) {
            }
          } catch (UnknownHostException t) {
            // no internet
            Debug.outNoStack(
                "VersionCheckClient - " + t.getClass().getName() + ": " + t.getMessage());
          } catch (IOException t) {
            // General connection problem.
            Debug.outNoStack(
                "VersionCheckClient - " + t.getClass().getName() + ": " + t.getMessage());
          } catch (Throwable t) {
            Debug.out(t);
            last_check_data_v4 = new HashMap();
          }
        } else {
          if (Logger.isEnabled())
            Logger.log(
                new LogEvent(
                    LOGID,
                    "VersionCheckClient is using "
                        + "cached version check info. Using "
                        + last_check_data_v4.size()
                        + " reply keys."));
        }
      } finally {
        check_mon.exit();
      }

      if (last_check_data_v4 == null) last_check_data_v4 = new HashMap();

      last_feature_flag_cache_time = 0;

      return last_check_data_v4;
    }
  }
  /**
   * Construct the default version check message.
   *
   * @return message to send
   */
  public static Map constructVersionCheckMessage(String reason) {

    // only send if anonymous-check flag is not set

    boolean send_info = COConfigurationManager.getBooleanParameter("Send Version Info");

    Map message = new HashMap();

    // always send
    message.put("appid", SystemProperties.getApplicationIdentifier());
    message.put("appname", SystemProperties.getApplicationName());
    message.put("version", Constants.AZUREUS_VERSION);

    String sub_ver = Constants.AZUREUS_SUBVER;

    if (sub_ver.length() > 0) {
      message.put("subver", sub_ver);
    }

    if (COConfigurationManager.getBooleanParameter("Beta Programme Enabled")) {

      message.put("beta_prog", "true");
    }

    message.put("ui", COConfigurationManager.getStringParameter("ui", "unknown"));
    message.put("os", Constants.OSName);
    message.put("os_version", System.getProperty("os.version"));
    message.put(
        "os_arch", System.getProperty("os.arch")); // see http://lopica.sourceforge.net/os.html

    boolean using_phe =
        COConfigurationManager.getBooleanParameter("network.transport.encrypted.require");
    message.put("using_phe", using_phe ? new Long(1) : new Long(0));

    // swt stuff
    try {
      Class c = Class.forName("org.eclipse.swt.SWT");

      String swt_platform =
          (String) c.getMethod("getPlatform", new Class[] {}).invoke(null, new Object[] {});
      message.put("swt_platform", swt_platform);

      Integer swt_version =
          (Integer) c.getMethod("getVersion", new Class[] {}).invoke(null, new Object[] {});
      message.put("swt_version", new Long(swt_version.longValue()));

      if (send_info) {
        c = Class.forName("org.gudy.azureus2.ui.swt.mainwindow.MainWindow");
        if (c != null) {
          c.getMethod("addToVersionCheckMessage", new Class[] {Map.class})
              .invoke(null, new Object[] {message});
        }
      }
    } catch (ClassNotFoundException e) {
      /* ignore */
    } catch (NoClassDefFoundError er) {
      /* ignore */
    } catch (InvocationTargetException err) {
      /* ignore */
    } catch (Throwable t) {
      t.printStackTrace();
    }

    int last_send_time = COConfigurationManager.getIntParameter("Send Version Info Last Time", -1);
    int current_send_time = (int) (SystemTime.getCurrentTime() / 1000);
    COConfigurationManager.setParameter("Send Version Info Last Time", current_send_time);

    String id = COConfigurationManager.getStringParameter("ID", null);

    if (id != null && send_info) {
      message.put("id", id);

      try {
        byte[] id2 = CryptoManagerFactory.getSingleton().getSecureID();

        message.put("id2", id2);

      } catch (Throwable e) {
      }

      if (last_send_time != -1 && last_send_time < current_send_time) {
        // time since last
        message.put("tsl", new Long(current_send_time - last_send_time));
      }

      message.put("reason", reason);

      String java_version = System.getProperty("java.version");
      if (java_version == null) {
        java_version = "unknown";
      }
      message.put("java", java_version);

      String java_vendor = System.getProperty("java.vm.vendor");
      if (java_vendor == null) {
        java_vendor = "unknown";
      }
      message.put("javavendor", java_vendor);

      long max_mem = Runtime.getRuntime().maxMemory() / (1024 * 1024);
      message.put("javamx", new Long(max_mem));

      String java_rt_name = System.getProperty("java.runtime.name");
      if (java_rt_name != null) {
        message.put("java_rt_name", java_rt_name);
      }

      String java_rt_version = System.getProperty("java.runtime.version");
      if (java_rt_version != null) {
        message.put("java_rt_version", java_rt_version);
      }

      OverallStats stats = StatsFactory.getStats();

      if (stats != null) {

        // long total_bytes_downloaded 	= stats.getDownloadedBytes();
        // long total_bytes_uploaded		= stats.getUploadedBytes();
        long total_uptime = stats.getTotalUpTime();

        // removed due to complaints about anonymous stats collection
        // message.put( "total_bytes_downloaded", new Long( total_bytes_downloaded ) );
        // message.put( "total_bytes_uploaded", new Long( total_bytes_uploaded ) );
        message.put("total_uptime", new Long(total_uptime));
        // message.put( "dlstats", stats.getDownloadStats());
      }

      try {
        NetworkAdminASN current_asn = NetworkAdmin.getSingleton().getCurrentASN();

        String as = current_asn.getAS();

        message.put("ip_as", current_asn.getAS());

        String asn = current_asn.getASName();

        if (asn.length() > 64) {

          asn = asn.substring(0, 64);
        }

        message.put("ip_asn", asn);

      } catch (Throwable e) {

        Debug.out(e);
      }

      // send locale, so we can determine which languages need attention
      message.put("locale", Locale.getDefault().toString());
      String originalLocale =
          System.getProperty("user.language") + "_" + System.getProperty("user.country");
      String variant = System.getProperty("user.variant");
      if (variant != null && variant.length() > 0) {
        originalLocale += "_" + variant;
      }
      message.put("orig_locale", originalLocale);

      // We may want to reply differently if the user is in Beginner mode vs Advanced
      message.put("user_mode", COConfigurationManager.getIntParameter("User Mode", -1));

      Set<String> features = UtilitiesImpl.getFeaturesInstalled();

      if (features.size() > 0) {

        String str = "";

        for (String f : features) {
          str += (str.length() == 0 ? "" : ",") + f;
        }

        message.put("vzfeatures", str);
      }

      try {
        if (AzureusCoreFactory.isCoreAvailable()) {

          // installed plugin IDs
          PluginInterface[] plugins =
              AzureusCoreFactory.getSingleton().getPluginManager().getPluginInterfaces();

          List pids = new ArrayList();

          List vs_data = new ArrayList();

          for (int i = 0; i < plugins.length; i++) {

            PluginInterface plugin = plugins[i];

            String pid = plugin.getPluginID();

            String info = plugin.getPluginconfig().getPluginStringParameter("plugin.info");

            // filter out built-in and core ones
            if ((info != null && info.length() > 0)
                || (!pid.startsWith("<")
                    && !pid.startsWith("azbp")
                    && !pid.startsWith("azupdater")
                    && !pid.startsWith("azplatform")
                    && !pids.contains(pid))) {

              if (info != null && info.length() > 0) {

                if (info.length() < 256) {

                  pid += ":" + info;

                } else {

                  Debug.out("Plugin '" + pid + "' reported excessive info string '" + info + "'");
                }
              }

              pids.add(pid);
            }

            Map data =
                plugin.getPluginconfig().getPluginMapParameter("plugin.versionserver.data", null);

            if (data != null) {

              Map payload = new HashMap();

              byte[] data_bytes = BEncoder.encode(data);

              if (data_bytes.length > 16 * 1024) {

                Debug.out(
                    "Plugin '"
                        + pid
                        + "' reported excessive version server data (length="
                        + data_bytes.length
                        + ")");

                payload.put("error", "data too long: " + data_bytes.length);

              } else {

                payload.put("data", data_bytes);
              }

              payload.put("id", pid);
              payload.put("version", plugin.getPluginVersion());

              vs_data.add(payload);
            }
          }
          message.put("plugins", pids);

          if (vs_data.size() > 0) {

            message.put("plugin_data", vs_data);
          }
        }
      } catch (Throwable e) {

        Debug.out(e);
      }
    }

    return message;
  }
  protected void preProcessReply(Map reply, final boolean v6) {
    NetworkAdmin admin = NetworkAdmin.getSingleton();

    try {
      byte[] address = (byte[]) reply.get("source_ip_address");

      InetAddress my_ip = InetAddress.getByName(new String(address));

      NetworkAdminASN old_asn = admin.getCurrentASN();

      NetworkAdminASN new_asn = admin.lookupCurrentASN(my_ip);

      if (!new_asn.sameAs(old_asn)) {

        // kick off a secondary version check to communicate the new information

        if (!secondary_check_done) {

          secondary_check_done = true;

          new AEThread("Secondary version check", true) {
            public void runSupport() {
              getVersionCheckInfoSupport(REASON_SECONDARY_CHECK, false, true, v6);
            }
          }.start();
        }
      }
    } catch (Throwable e) {

      Debug.printStackTrace(e);
    }

    Long as_advice = (Long) reply.get("as_advice");

    if (as_advice != null) {

      NetworkAdminASN current_asn = admin.getCurrentASN();

      String asn = current_asn.getASName();

      if (asn != null) {

        long advice = as_advice.longValue();

        if (advice != 0) {

          // require crypto

          String done_asn = COConfigurationManager.getStringParameter("ASN Advice Followed", "");

          if (!done_asn.equals(asn)) {

            COConfigurationManager.setParameter("ASN Advice Followed", asn);

            boolean change = advice == 1 || advice == 2;
            boolean alert = advice == 1 || advice == 3;

            if (!COConfigurationManager.getBooleanParameter(
                "network.transport.encrypted.require")) {

              if (change) {

                COConfigurationManager.setParameter("network.transport.encrypted.require", true);
              }

              if (alert) {

                String msg = MessageText.getString("crypto.alert.as.warning", new String[] {asn});

                Logger.log(new LogAlert(false, LogAlert.AT_WARNING, msg));
              }
            }
          }
        }
      }
    }

    // set ui.toolbar.uiswitcher based on instructions from tracker
    // Really shouldn't be in VersionCheck client, but instead have some
    // listener and have the code elsewhere.  Simply calling
    // getVersionCheckInfo from "code elsewhere" (to get the cached result)
    // caused a deadlock at startup.
    Long lEnabledUISwitcher = (Long) reply.get("ui.toolbar.uiswitcher");
    if (lEnabledUISwitcher != null) {
      COConfigurationManager.setBooleanDefault(
          "ui.toolbar.uiswitcher", lEnabledUISwitcher.longValue() == 1);
    }
  }