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

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 package org.mozilla.gecko.background.healthreport;
michael@0 6
michael@0 7 import java.io.UnsupportedEncodingException;
michael@0 8 import java.security.NoSuchAlgorithmException;
michael@0 9 import java.util.Iterator;
michael@0 10 import java.util.SortedSet;
michael@0 11
michael@0 12 import org.json.JSONException;
michael@0 13 import org.json.JSONObject;
michael@0 14 import org.mozilla.apache.commons.codec.binary.Base64;
michael@0 15 import org.mozilla.gecko.background.common.log.Logger;
michael@0 16 import org.mozilla.gecko.background.nativecode.NativeCrypto;
michael@0 17
michael@0 18 public abstract class EnvironmentV1 {
michael@0 19 private static final String LOG_TAG = "GeckoEnvironment";
michael@0 20 private static final int VERSION = 1;
michael@0 21
michael@0 22 protected final Class<? extends EnvironmentAppender> appenderClass;
michael@0 23
michael@0 24 protected volatile String hash = null;
michael@0 25 protected volatile int id = -1;
michael@0 26
michael@0 27 public int version = VERSION;
michael@0 28
michael@0 29 // org.mozilla.profile.age.
michael@0 30 public int profileCreation;
michael@0 31
michael@0 32 // org.mozilla.sysinfo.sysinfo.
michael@0 33 public int cpuCount;
michael@0 34 public int memoryMB;
michael@0 35 public String architecture;
michael@0 36 public String sysName;
michael@0 37 public String sysVersion; // Kernel.
michael@0 38
michael@0 39 // geckoAppInfo.
michael@0 40 public String vendor;
michael@0 41 public String appName;
michael@0 42 public String appID;
michael@0 43 public String appVersion;
michael@0 44 public String appBuildID;
michael@0 45 public String platformVersion;
michael@0 46 public String platformBuildID;
michael@0 47 public String os;
michael@0 48 public String xpcomabi;
michael@0 49 public String updateChannel;
michael@0 50
michael@0 51 // appinfo.
michael@0 52 public int isBlocklistEnabled;
michael@0 53 public int isTelemetryEnabled;
michael@0 54
michael@0 55 // org.mozilla.addons.active.
michael@0 56 public JSONObject addons = null;
michael@0 57
michael@0 58 // org.mozilla.addons.counts.
michael@0 59 public int extensionCount;
michael@0 60 public int pluginCount;
michael@0 61 public int themeCount;
michael@0 62
michael@0 63 /**
michael@0 64 * We break out this interface in order to allow for testing -- pass in your
michael@0 65 * own appender that just records strings, for example.
michael@0 66 */
michael@0 67 public static abstract class EnvironmentAppender {
michael@0 68 public abstract void append(String s);
michael@0 69 public abstract void append(int v);
michael@0 70 }
michael@0 71
michael@0 72 public static class HashAppender extends EnvironmentAppender {
michael@0 73 private final StringBuilder builder;
michael@0 74
michael@0 75 public HashAppender() throws NoSuchAlgorithmException {
michael@0 76 builder = new StringBuilder();
michael@0 77 }
michael@0 78
michael@0 79 @Override
michael@0 80 public void append(String s) {
michael@0 81 builder.append((s == null) ? "null" : s);
michael@0 82 }
michael@0 83
michael@0 84 @Override
michael@0 85 public void append(int profileCreation) {
michael@0 86 append(Integer.toString(profileCreation, 10));
michael@0 87 }
michael@0 88
michael@0 89 @Override
michael@0 90 public String toString() {
michael@0 91 // We *could* use ASCII85… but the savings would be negated by the
michael@0 92 // inclusion of JSON-unsafe characters like double-quote.
michael@0 93 final byte[] inputBytes;
michael@0 94 try {
michael@0 95 inputBytes = builder.toString().getBytes("UTF-8");
michael@0 96 } catch (UnsupportedEncodingException e) {
michael@0 97 Logger.warn(LOG_TAG, "Invalid charset String passed to getBytes", e);
michael@0 98 return null;
michael@0 99 }
michael@0 100
michael@0 101 // Note to the security-minded reader: we deliberately use SHA-1 here, not
michael@0 102 // a stronger hash. These identifiers don't strictly need a cryptographic
michael@0 103 // hash function, because there is negligible value in attacking the hash.
michael@0 104 // We use SHA-1 because it's *shorter* -- the exact same reason that Git
michael@0 105 // chose SHA-1.
michael@0 106 final byte[] hash = NativeCrypto.sha1(inputBytes);
michael@0 107 return new Base64(-1, null, false).encodeAsString(hash);
michael@0 108 }
michael@0 109 }
michael@0 110
michael@0 111 /**
michael@0 112 * Ensure that the {@link Environment} has been registered with its
michael@0 113 * storage layer, and can be used to annotate events.
michael@0 114 *
michael@0 115 * It's safe to call this method more than once, and each time you'll
michael@0 116 * get the same ID.
michael@0 117 *
michael@0 118 * @return the integer ID to use in subsequent DB insertions.
michael@0 119 */
michael@0 120 public abstract int register();
michael@0 121
michael@0 122 protected EnvironmentAppender getAppender() {
michael@0 123 EnvironmentAppender appender = null;
michael@0 124 try {
michael@0 125 appender = appenderClass.newInstance();
michael@0 126 } catch (InstantiationException ex) {
michael@0 127 // Should never happen, but...
michael@0 128 Logger.warn(LOG_TAG, "Could not compute hash.", ex);
michael@0 129 } catch (IllegalAccessException ex) {
michael@0 130 // Should never happen, but...
michael@0 131 Logger.warn(LOG_TAG, "Could not compute hash.", ex);
michael@0 132 }
michael@0 133 return appender;
michael@0 134 }
michael@0 135
michael@0 136 protected void appendHash(EnvironmentAppender appender) {
michael@0 137 appender.append(profileCreation);
michael@0 138 appender.append(cpuCount);
michael@0 139 appender.append(memoryMB);
michael@0 140 appender.append(architecture);
michael@0 141 appender.append(sysName);
michael@0 142 appender.append(sysVersion);
michael@0 143 appender.append(vendor);
michael@0 144 appender.append(appName);
michael@0 145 appender.append(appID);
michael@0 146 appender.append(appVersion);
michael@0 147 appender.append(appBuildID);
michael@0 148 appender.append(platformVersion);
michael@0 149 appender.append(platformBuildID);
michael@0 150 appender.append(os);
michael@0 151 appender.append(xpcomabi);
michael@0 152 appender.append(updateChannel);
michael@0 153 appender.append(isBlocklistEnabled);
michael@0 154 appender.append(isTelemetryEnabled);
michael@0 155 appender.append(extensionCount);
michael@0 156 appender.append(pluginCount);
michael@0 157 appender.append(themeCount);
michael@0 158
michael@0 159 // We need sorted values.
michael@0 160 if (addons != null) {
michael@0 161 appendSortedAddons(getNonIgnoredAddons(), appender);
michael@0 162 }
michael@0 163 }
michael@0 164
michael@0 165 /**
michael@0 166 * Compute the stable hash of the configured environment.
michael@0 167 *
michael@0 168 * @return the hash in base34, or null if there was a problem.
michael@0 169 */
michael@0 170 public String getHash() {
michael@0 171 // It's never unset, so we only care about partial reads. volatile is enough.
michael@0 172 if (hash != null) {
michael@0 173 return hash;
michael@0 174 }
michael@0 175
michael@0 176 EnvironmentAppender appender = getAppender();
michael@0 177 if (appender == null) {
michael@0 178 return null;
michael@0 179 }
michael@0 180
michael@0 181 appendHash(appender);
michael@0 182 return hash = appender.toString();
michael@0 183 }
michael@0 184
michael@0 185 public EnvironmentV1(Class<? extends EnvironmentAppender> appenderClass) {
michael@0 186 super();
michael@0 187 this.appenderClass = appenderClass;
michael@0 188 }
michael@0 189
michael@0 190 public JSONObject getNonIgnoredAddons() {
michael@0 191 if (addons == null) {
michael@0 192 return null;
michael@0 193 }
michael@0 194 JSONObject out = new JSONObject();
michael@0 195 @SuppressWarnings("unchecked")
michael@0 196 Iterator<String> keys = addons.keys();
michael@0 197 while (keys.hasNext()) {
michael@0 198 try {
michael@0 199 final String key = keys.next();
michael@0 200 final Object obj = addons.get(key);
michael@0 201 if (obj != null &&
michael@0 202 obj instanceof JSONObject &&
michael@0 203 ((JSONObject) obj).optBoolean("ignore", false)) {
michael@0 204 continue;
michael@0 205 }
michael@0 206 out.put(key, obj);
michael@0 207 } catch (JSONException ex) {
michael@0 208 // Do nothing.
michael@0 209 }
michael@0 210 }
michael@0 211 return out;
michael@0 212 }
michael@0 213
michael@0 214 /**
michael@0 215 * Take a collection of add-on descriptors, appending a consistent string
michael@0 216 * to the provided builder.
michael@0 217 */
michael@0 218 public static void appendSortedAddons(JSONObject addons, final EnvironmentAppender builder) {
michael@0 219 final SortedSet<String> keys = HealthReportUtils.sortedKeySet(addons);
michael@0 220
michael@0 221 // For each add-on, produce a consistent, sorted mapping of its descriptor.
michael@0 222 for (String key : keys) {
michael@0 223 try {
michael@0 224 JSONObject addon = addons.getJSONObject(key);
michael@0 225
michael@0 226 // Now produce the output for this add-on.
michael@0 227 builder.append(key);
michael@0 228 builder.append("={");
michael@0 229
michael@0 230 for (String addonKey : HealthReportUtils.sortedKeySet(addon)) {
michael@0 231 builder.append(addonKey);
michael@0 232 builder.append("==");
michael@0 233 try {
michael@0 234 builder.append(addon.get(addonKey).toString());
michael@0 235 } catch (JSONException e) {
michael@0 236 builder.append("_e_");
michael@0 237 }
michael@0 238 }
michael@0 239
michael@0 240 builder.append("}");
michael@0 241 } catch (Exception e) {
michael@0 242 // Muffle.
michael@0 243 Logger.warn(LOG_TAG, "Invalid add-on for ID " + key);
michael@0 244 }
michael@0 245 }
michael@0 246 }
michael@0 247
michael@0 248 public void setJSONForAddons(byte[] json) throws Exception {
michael@0 249 setJSONForAddons(new String(json, "UTF-8"));
michael@0 250 }
michael@0 251
michael@0 252 public void setJSONForAddons(String json) throws Exception {
michael@0 253 if (json == null || "null".equals(json)) {
michael@0 254 addons = null;
michael@0 255 return;
michael@0 256 }
michael@0 257 addons = new JSONObject(json);
michael@0 258 }
michael@0 259
michael@0 260 public void setJSONForAddons(JSONObject json) {
michael@0 261 addons = json;
michael@0 262 }
michael@0 263
michael@0 264 /**
michael@0 265 * Includes ignored add-ons.
michael@0 266 */
michael@0 267 public String getNormalizedAddonsJSON() {
michael@0 268 // We trust that our input will already be normalized. If that assumption
michael@0 269 // is invalidated, then we'll be sorry.
michael@0 270 return (addons == null) ? "null" : addons.toString();
michael@0 271 }
michael@0 272 }

mercurial