mobile/android/base/background/healthreport/EnvironmentV1.java

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/mobile/android/base/background/healthreport/EnvironmentV1.java	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,272 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +package org.mozilla.gecko.background.healthreport;
     1.9 +
    1.10 +import java.io.UnsupportedEncodingException;
    1.11 +import java.security.NoSuchAlgorithmException;
    1.12 +import java.util.Iterator;
    1.13 +import java.util.SortedSet;
    1.14 +
    1.15 +import org.json.JSONException;
    1.16 +import org.json.JSONObject;
    1.17 +import org.mozilla.apache.commons.codec.binary.Base64;
    1.18 +import org.mozilla.gecko.background.common.log.Logger;
    1.19 +import org.mozilla.gecko.background.nativecode.NativeCrypto;
    1.20 +
    1.21 +public abstract class EnvironmentV1 {
    1.22 +  private static final String LOG_TAG = "GeckoEnvironment";
    1.23 +  private static final int VERSION = 1;
    1.24 +
    1.25 +  protected final Class<? extends EnvironmentAppender> appenderClass;
    1.26 +
    1.27 +  protected volatile String hash = null;
    1.28 +  protected volatile int id = -1;
    1.29 +
    1.30 +  public int version = VERSION;
    1.31 +
    1.32 +  // org.mozilla.profile.age.
    1.33 +  public int profileCreation;
    1.34 +
    1.35 +  // org.mozilla.sysinfo.sysinfo.
    1.36 +  public int cpuCount;
    1.37 +  public int memoryMB;
    1.38 +  public String architecture;
    1.39 +  public String sysName;
    1.40 +  public String sysVersion;       // Kernel.
    1.41 +
    1.42 +  // geckoAppInfo.
    1.43 +  public String vendor;
    1.44 +  public String appName;
    1.45 +  public String appID;
    1.46 +  public String appVersion;
    1.47 +  public String appBuildID;
    1.48 +  public String platformVersion;
    1.49 +  public String platformBuildID;
    1.50 +  public String os;
    1.51 +  public String xpcomabi;
    1.52 +  public String updateChannel;
    1.53 +
    1.54 +  // appinfo.
    1.55 +  public int isBlocklistEnabled;
    1.56 +  public int isTelemetryEnabled;
    1.57 +
    1.58 +  // org.mozilla.addons.active.
    1.59 +  public JSONObject addons = null;
    1.60 +
    1.61 +  // org.mozilla.addons.counts.
    1.62 +  public int extensionCount;
    1.63 +  public int pluginCount;
    1.64 +  public int themeCount;
    1.65 +
    1.66 +  /**
    1.67 +   * We break out this interface in order to allow for testing -- pass in your
    1.68 +   * own appender that just records strings, for example.
    1.69 +   */
    1.70 +  public static abstract class EnvironmentAppender {
    1.71 +    public abstract void append(String s);
    1.72 +    public abstract void append(int v);
    1.73 +  }
    1.74 +
    1.75 +  public static class HashAppender extends EnvironmentAppender {
    1.76 +    private final StringBuilder builder;
    1.77 +
    1.78 +    public HashAppender() throws NoSuchAlgorithmException {
    1.79 +      builder = new StringBuilder();
    1.80 +    }
    1.81 +
    1.82 +    @Override
    1.83 +    public void append(String s) {
    1.84 +      builder.append((s == null) ? "null" : s);
    1.85 +    }
    1.86 +
    1.87 +    @Override
    1.88 +    public void append(int profileCreation) {
    1.89 +      append(Integer.toString(profileCreation, 10));
    1.90 +    }
    1.91 +
    1.92 +    @Override
    1.93 +    public String toString() {
    1.94 +      // We *could* use ASCII85… but the savings would be negated by the
    1.95 +      // inclusion of JSON-unsafe characters like double-quote.
    1.96 +      final byte[] inputBytes;
    1.97 +      try {
    1.98 +        inputBytes = builder.toString().getBytes("UTF-8");
    1.99 +      } catch (UnsupportedEncodingException e) {
   1.100 +        Logger.warn(LOG_TAG, "Invalid charset String passed to getBytes", e);
   1.101 +        return null;
   1.102 +      }
   1.103 +
   1.104 +      // Note to the security-minded reader: we deliberately use SHA-1 here, not
   1.105 +      // a stronger hash. These identifiers don't strictly need a cryptographic
   1.106 +      // hash function, because there is negligible value in attacking the hash.
   1.107 +      // We use SHA-1 because it's *shorter* -- the exact same reason that Git
   1.108 +      // chose SHA-1.
   1.109 +      final byte[] hash = NativeCrypto.sha1(inputBytes);
   1.110 +      return new Base64(-1, null, false).encodeAsString(hash);
   1.111 +    }
   1.112 +  }
   1.113 +
   1.114 +  /**
   1.115 +   * Ensure that the {@link Environment} has been registered with its
   1.116 +   * storage layer, and can be used to annotate events.
   1.117 +   *
   1.118 +   * It's safe to call this method more than once, and each time you'll
   1.119 +   * get the same ID.
   1.120 +   *
   1.121 +   * @return the integer ID to use in subsequent DB insertions.
   1.122 +   */
   1.123 +  public abstract int register();
   1.124 +
   1.125 +  protected EnvironmentAppender getAppender() {
   1.126 +    EnvironmentAppender appender = null;
   1.127 +    try {
   1.128 +      appender = appenderClass.newInstance();
   1.129 +    } catch (InstantiationException ex) {
   1.130 +      // Should never happen, but...
   1.131 +      Logger.warn(LOG_TAG,  "Could not compute hash.", ex);
   1.132 +    } catch (IllegalAccessException ex) {
   1.133 +      // Should never happen, but...
   1.134 +      Logger.warn(LOG_TAG,  "Could not compute hash.", ex);
   1.135 +    }
   1.136 +    return appender;
   1.137 +  }
   1.138 +
   1.139 +  protected void appendHash(EnvironmentAppender appender) {
   1.140 +    appender.append(profileCreation);
   1.141 +    appender.append(cpuCount);
   1.142 +    appender.append(memoryMB);
   1.143 +    appender.append(architecture);
   1.144 +    appender.append(sysName);
   1.145 +    appender.append(sysVersion);
   1.146 +    appender.append(vendor);
   1.147 +    appender.append(appName);
   1.148 +    appender.append(appID);
   1.149 +    appender.append(appVersion);
   1.150 +    appender.append(appBuildID);
   1.151 +    appender.append(platformVersion);
   1.152 +    appender.append(platformBuildID);
   1.153 +    appender.append(os);
   1.154 +    appender.append(xpcomabi);
   1.155 +    appender.append(updateChannel);
   1.156 +    appender.append(isBlocklistEnabled);
   1.157 +    appender.append(isTelemetryEnabled);
   1.158 +    appender.append(extensionCount);
   1.159 +    appender.append(pluginCount);
   1.160 +    appender.append(themeCount);
   1.161 +
   1.162 +    // We need sorted values.
   1.163 +    if (addons != null) {
   1.164 +      appendSortedAddons(getNonIgnoredAddons(), appender);
   1.165 +    }
   1.166 +  }
   1.167 +
   1.168 +  /**
   1.169 +   * Compute the stable hash of the configured environment.
   1.170 +   *
   1.171 +   * @return the hash in base34, or null if there was a problem.
   1.172 +   */
   1.173 +  public String getHash() {
   1.174 +    // It's never unset, so we only care about partial reads. volatile is enough.
   1.175 +    if (hash != null) {
   1.176 +      return hash;
   1.177 +    }
   1.178 +
   1.179 +    EnvironmentAppender appender = getAppender();
   1.180 +    if (appender == null) {
   1.181 +      return null;
   1.182 +    }
   1.183 +
   1.184 +    appendHash(appender);
   1.185 +    return hash = appender.toString();
   1.186 +  }
   1.187 +
   1.188 +  public EnvironmentV1(Class<? extends EnvironmentAppender> appenderClass) {
   1.189 +    super();
   1.190 +    this.appenderClass = appenderClass;
   1.191 +  }
   1.192 +
   1.193 +  public JSONObject getNonIgnoredAddons() {
   1.194 +    if (addons == null) {
   1.195 +      return null;
   1.196 +    }
   1.197 +    JSONObject out = new JSONObject();
   1.198 +    @SuppressWarnings("unchecked")
   1.199 +    Iterator<String> keys = addons.keys();
   1.200 +    while (keys.hasNext()) {
   1.201 +      try {
   1.202 +        final String key = keys.next();
   1.203 +        final Object obj = addons.get(key);
   1.204 +        if (obj != null &&
   1.205 +            obj instanceof JSONObject &&
   1.206 +            ((JSONObject) obj).optBoolean("ignore", false)) {
   1.207 +          continue;
   1.208 +        }
   1.209 +        out.put(key, obj);
   1.210 +      } catch (JSONException ex) {
   1.211 +        // Do nothing.
   1.212 +      }
   1.213 +    }
   1.214 +    return out;
   1.215 +  }
   1.216 +
   1.217 +  /**
   1.218 +   * Take a collection of add-on descriptors, appending a consistent string
   1.219 +   * to the provided builder.
   1.220 +   */
   1.221 +  public static void appendSortedAddons(JSONObject addons, final EnvironmentAppender builder) {
   1.222 +    final SortedSet<String> keys = HealthReportUtils.sortedKeySet(addons);
   1.223 +
   1.224 +    // For each add-on, produce a consistent, sorted mapping of its descriptor.
   1.225 +    for (String key : keys) {
   1.226 +      try {
   1.227 +        JSONObject addon = addons.getJSONObject(key);
   1.228 +
   1.229 +        // Now produce the output for this add-on.
   1.230 +        builder.append(key);
   1.231 +        builder.append("={");
   1.232 +
   1.233 +        for (String addonKey : HealthReportUtils.sortedKeySet(addon)) {
   1.234 +          builder.append(addonKey);
   1.235 +          builder.append("==");
   1.236 +          try {
   1.237 +            builder.append(addon.get(addonKey).toString());
   1.238 +          } catch (JSONException e) {
   1.239 +            builder.append("_e_");
   1.240 +          }
   1.241 +        }
   1.242 +
   1.243 +        builder.append("}");
   1.244 +      } catch (Exception e) {
   1.245 +        // Muffle.
   1.246 +        Logger.warn(LOG_TAG, "Invalid add-on for ID " + key);
   1.247 +      }
   1.248 +    }
   1.249 +  }
   1.250 +
   1.251 +  public void setJSONForAddons(byte[] json) throws Exception {
   1.252 +    setJSONForAddons(new String(json, "UTF-8"));
   1.253 +  }
   1.254 +
   1.255 +  public void setJSONForAddons(String json) throws Exception {
   1.256 +    if (json == null || "null".equals(json)) {
   1.257 +      addons = null;
   1.258 +      return;
   1.259 +    }
   1.260 +    addons = new JSONObject(json);
   1.261 +  }
   1.262 +
   1.263 +  public void setJSONForAddons(JSONObject json) {
   1.264 +    addons = json;
   1.265 +  }
   1.266 +
   1.267 +  /**
   1.268 +   * Includes ignored add-ons.
   1.269 +   */
   1.270 +  public String getNormalizedAddonsJSON() {
   1.271 +    // We trust that our input will already be normalized. If that assumption
   1.272 +    // is invalidated, then we'll be sorry.
   1.273 +    return (addons == null) ? "null" : addons.toString();
   1.274 +  }
   1.275 +}

mercurial