• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2023 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.net.telemetry;
6 
7 import android.util.Log;
8 
9 import org.json.JSONException;
10 import org.json.JSONObject;
11 import org.json.JSONTokener;
12 
13 import java.util.ArrayList;
14 import java.util.Locale;
15 import java.util.Set;
16 
17 /** Parses the experimentalOptions string */
18 public final class ExperimentalOptions {
19     private static final String TAG = ExperimentalOptions.class.getSimpleName();
20 
21     public static final int UNSET_INT_VALUE = -1;
22 
23     // Declare static experimental options field trial names
24     private static final String QUIC = "QUIC";
25     private static final String ASYNC_DNS = "AsyncDNS";
26     private static final String STALE_DNS = "StaleDNS";
27 
28     // The JSONObject created from the experimentalOptions String
29     private JSONObject mJson = new JSONObject();
30 
ExperimentalOptions(String experimentalOptions)31     public ExperimentalOptions(String experimentalOptions) {
32         if (!isNullOrEmpty(experimentalOptions)) {
33             try {
34                 mJson = (JSONObject) new JSONTokener(experimentalOptions).nextValue();
35             } catch (JSONException | ClassCastException e) {
36                 Log.d(
37                         TAG,
38                         String.format(
39                                 "Experimental options could not be parsed, using default values."
40                                         + " Error: %s",
41                                 e.getMessage()));
42             }
43         }
44     }
45 
getConnectionOptionsOption()46     public String getConnectionOptionsOption() {
47         return parseExperimentalOptionsString(
48                 getOrDefault(QUIC, "connection_options", null, String.class));
49     }
50 
getStoreServerConfigsInPropertiesOption()51     public OptionalBoolean getStoreServerConfigsInPropertiesOption() {
52         return OptionalBoolean.fromBoolean(
53                 getOrDefault(QUIC, "store_server_configs_in_properties", null, Boolean.class));
54     }
55 
getMaxServerConfigsStoredInPropertiesOption()56     public int getMaxServerConfigsStoredInPropertiesOption() {
57         return getOrDefault(
58                 QUIC, "max_server_configs_stored_in_properties", UNSET_INT_VALUE, Integer.class);
59     }
60 
61     @SuppressWarnings("GoodTime") // CronetStatsLog expects int
getIdleConnectionTimeoutSecondsOption()62     public int getIdleConnectionTimeoutSecondsOption() {
63         return getOrDefault(
64                 QUIC, "idle_connection_timeout_seconds", UNSET_INT_VALUE, Integer.class);
65     }
66 
getGoawaySessionsOnIpChangeOption()67     public OptionalBoolean getGoawaySessionsOnIpChangeOption() {
68         return OptionalBoolean.fromBoolean(
69                 getOrDefault(QUIC, "goaway_sessions_on_ip_change", null, Boolean.class));
70     }
71 
getCloseSessionsOnIpChangeOption()72     public OptionalBoolean getCloseSessionsOnIpChangeOption() {
73         return OptionalBoolean.fromBoolean(
74                 getOrDefault(QUIC, "close_sessions_on_ip_change", null, Boolean.class));
75     }
76 
getMigrateSessionsOnNetworkChangeV2Option()77     public OptionalBoolean getMigrateSessionsOnNetworkChangeV2Option() {
78         return OptionalBoolean.fromBoolean(
79                 getOrDefault(QUIC, "migrate_sessions_on_network_change_v2", null, Boolean.class));
80     }
81 
getMigrateSessionsEarlyV2()82     public OptionalBoolean getMigrateSessionsEarlyV2() {
83         return OptionalBoolean.fromBoolean(
84                 getOrDefault(QUIC, "migrate_sessions_early_v2", null, Boolean.class));
85     }
86 
getDisableBidirectionalStreamsOption()87     public OptionalBoolean getDisableBidirectionalStreamsOption() {
88         return OptionalBoolean.fromBoolean(
89                 getOrDefault(QUIC, "disable_bidirectional_streams", null, Boolean.class));
90     }
91 
92     @SuppressWarnings("GoodTime") // CronetStatsLog expects int
getMaxTimeBeforeCryptoHandshakeSecondsOption()93     public int getMaxTimeBeforeCryptoHandshakeSecondsOption() {
94         return getOrDefault(
95                 QUIC, "max_time_before_crypto_handshake_seconds", UNSET_INT_VALUE, Integer.class);
96     }
97 
98     @SuppressWarnings("GoodTime") // CronetStatsLog expects int
getMaxIdleTimeBeforeCryptoHandshakeSecondsOption()99     public int getMaxIdleTimeBeforeCryptoHandshakeSecondsOption() {
100         return getOrDefault(
101                 QUIC,
102                 "max_idle_time_before_crypto_handshake_seconds",
103                 UNSET_INT_VALUE,
104                 Integer.class);
105     }
106 
getEnableSocketRecvOptimizationOption()107     public OptionalBoolean getEnableSocketRecvOptimizationOption() {
108         return OptionalBoolean.fromBoolean(
109                 getOrDefault(QUIC, "enable_socket_recv_optimization", null, Boolean.class));
110     }
111 
getAsyncDnsEnableOption()112     public OptionalBoolean getAsyncDnsEnableOption() {
113         return OptionalBoolean.fromBoolean(getOrDefault(ASYNC_DNS, "enable", null, Boolean.class));
114     }
115 
getStaleDnsEnableOption()116     public OptionalBoolean getStaleDnsEnableOption() {
117         return OptionalBoolean.fromBoolean(getOrDefault(STALE_DNS, "enable", null, Boolean.class));
118     }
119 
120     @SuppressWarnings("GoodTime") // CronetStatsLog expects int
getStaleDnsDelayMillisOption()121     public int getStaleDnsDelayMillisOption() {
122         return getOrDefault(STALE_DNS, "delay_ms", UNSET_INT_VALUE, Integer.class);
123     }
124 
125     @SuppressWarnings("GoodTime") // CronetStatsLog expects int
getStaleDnsMaxExpiredTimeMillisOption()126     public int getStaleDnsMaxExpiredTimeMillisOption() {
127         return getOrDefault(STALE_DNS, "max_expired_time_ms", UNSET_INT_VALUE, Integer.class);
128     }
129 
getStaleDnsMaxStaleUsesOption()130     public int getStaleDnsMaxStaleUsesOption() {
131         return getOrDefault(STALE_DNS, "max_stale_uses", UNSET_INT_VALUE, Integer.class);
132     }
133 
getStaleDnsAllowOtherNetworkOption()134     public OptionalBoolean getStaleDnsAllowOtherNetworkOption() {
135         return OptionalBoolean.fromBoolean(
136                 getOrDefault(STALE_DNS, "allow_other_network", null, Boolean.class));
137     }
138 
getStaleDnsPersistToDiskOption()139     public OptionalBoolean getStaleDnsPersistToDiskOption() {
140         return OptionalBoolean.fromBoolean(
141                 getOrDefault(STALE_DNS, "persist_to_disk", null, Boolean.class));
142     }
143 
144     @SuppressWarnings("GoodTime") // CronetStatsLog expects int
getStaleDnsPersistDelayMillisOption()145     public int getStaleDnsPersistDelayMillisOption() {
146         return getOrDefault(STALE_DNS, "persist_delay_ms", UNSET_INT_VALUE, Integer.class);
147     }
148 
getStaleDnsUseStaleOnNameNotResolvedOption()149     public OptionalBoolean getStaleDnsUseStaleOnNameNotResolvedOption() {
150         return OptionalBoolean.fromBoolean(
151                 getOrDefault(STALE_DNS, "use_stale_on_name_not_resolved", null, Boolean.class));
152     }
153 
getDisableIpv6OnWifiOption()154     public OptionalBoolean getDisableIpv6OnWifiOption() {
155         return OptionalBoolean.fromBoolean(
156                 getOrDefault("disable_ipv6_on_wifi", null, Boolean.class));
157     }
158 
159     /**
160      * Checks if an experimentalOption fieldTrial key exists, then gets the value of the child
161      * option.
162      *
163      * @param experimentalOptionFieldTrialName the super option name for a nested experimental
164      *     option eg QUIC.connection_options where <code>QUIC</code> is the FieldTrialName and
165      *     <code>
166      *     connection_options</code> is the child option
167      * @param option the child option eg <code>connection_options</code>
168      * @param defaultValue the defaultValue if the option is null or empty
169      * @return the experimental option value
170      */
getOrDefault( String experimentalOptionFieldTrialName, String option, T defaultValue, Class<T> clazz)171     private <T> T getOrDefault(
172             String experimentalOptionFieldTrialName,
173             String option,
174             T defaultValue,
175             Class<T> clazz) {
176         // check if the field trial name exists
177         JSONObject options = null;
178         try {
179             options = mJson.getJSONObject(experimentalOptionFieldTrialName);
180         } catch (JSONException e) {
181             Log.d(
182                     TAG,
183                     String.format(
184                             "Failed to get %s options: %s",
185                             experimentalOptionFieldTrialName, e.getMessage()));
186         }
187 
188         if (options == null) {
189             return defaultValue;
190         }
191 
192         T value = defaultValue;
193         try {
194             value = clazz.cast(options.get(option));
195         } catch (JSONException | ClassCastException e) {
196             Log.d(TAG, String.format("Failed to get %s options: %s", option, e.getMessage()));
197         }
198         return value;
199     }
200 
getOrDefault(String option, T defaultValue, Class<T> clazz)201     private <T> T getOrDefault(String option, T defaultValue, Class<T> clazz) {
202         T value = defaultValue;
203         try {
204             value = clazz.cast(mJson.get(option));
205         } catch (JSONException | ClassCastException e) {
206             Log.d(TAG, String.format("Failed to get %s options: %s", option, e.getMessage()));
207         }
208         return value;
209     }
210 
211     /**
212      * Checks that the connection_options options are always valid and do not contain any PII.
213      * Removes any value that does not conform to a valid option.
214      */
parseExperimentalOptionsString(String str)215     private String parseExperimentalOptionsString(String str) {
216         if (isNullOrEmpty(str)) {
217             return str;
218         }
219 
220         ArrayList<String> nStr = new ArrayList<>();
221         for (String s : str.split(",", -1)) {
222             if (VALID_CONNECTION_OPTIONS.contains(s.toUpperCase(Locale.ROOT).trim())) {
223                 nStr.add(s);
224             }
225         }
226 
227         return String.join(",", nStr);
228     }
229 
230     /**
231      * The generated CronetStatsLog class has an optionalBoolean(UNSET,TRUE,FALSE) variable for each
232      * of the experimental options. Since these values will always be the same for the options, we
233      * picked one of them and used it to create a private variable that we can use to make the code
234      * more readable.
235      */
236     public static enum OptionalBoolean {
237         UNSET(
238                 CronetStatsLog
239                         .CRONET_ENGINE_CREATED__EXPERIMENTAL_OPTIONS_QUIC_STORE_SERVER_CONFIGS_IN_PROPERTIES__OPTIONAL_BOOLEAN_UNSET),
240         TRUE(
241                 CronetStatsLog
242                         .CRONET_ENGINE_CREATED__EXPERIMENTAL_OPTIONS_QUIC_STORE_SERVER_CONFIGS_IN_PROPERTIES__OPTIONAL_BOOLEAN_TRUE),
243         FALSE(
244                 CronetStatsLog
245                         .CRONET_ENGINE_CREATED__EXPERIMENTAL_OPTIONS_QUIC_STORE_SERVER_CONFIGS_IN_PROPERTIES__OPTIONAL_BOOLEAN_FALSE);
246 
247         private final int mValue;
248 
OptionalBoolean(int value)249         private OptionalBoolean(int value) {
250             this.mValue = value;
251         }
252 
getValue()253         public int getValue() {
254             return mValue;
255         }
256 
fromBoolean(Boolean value)257         public static OptionalBoolean fromBoolean(Boolean value) {
258             if (value == null) {
259                 return UNSET;
260             }
261 
262             return value ? TRUE : FALSE;
263         }
264     }
265 
isNullOrEmpty(String str)266     private boolean isNullOrEmpty(String str) {
267         return str == null || str.isEmpty();
268     }
269 
270     // Source:
271     // //external/cronet:net/third_party/quiche/src/quiche/quic/core/crypto/crypto_protocol.h
272     public static final Set<String> VALID_CONNECTION_OPTIONS =
273             Set.of(
274                     "CHLO", "SHLO", "SCFG", "REJ", "CETV", "PRST", "SCUP", "ALPN", "P256", "C255",
275                     "AESG", "CC20", "QBIC", "AFCW", "IFW5", "IFW6", "IFW7", "IFW8", "IFW9", "IFWA",
276                     "TBBR", "1RTT", "2RTT", "LRTT", "BBS1", "BBS2", "BBS3", "BBS4", "BBS5", "BBRR",
277                     "BBR1", "BBR2", "BBR3", "BBR4", "BBR5", "BBR9", "BBRA", "BBRB", "BBRS", "BBQ1",
278                     "BBQ2", "BBQ3", "BBQ5", "BBQ6", "BBQ7", "BBQ8", "BBQ9", "BBQ0", "RENO", "TPCC",
279                     "BYTE", "IW03", "IW10", "IW20", "IW50", "B2ON", "B2NA", "B2NE", "B2RP", "B2LO",
280                     "B2HR", "B2SL", "B2H2", "B2RC", "BSAO", "B2DL", "B201", "B202", "B203", "B204",
281                     "B205", "B206", "B207", "NTLP", "1TLP", "1RTO", "NRTO", "TIME", "ATIM", "MIN1",
282                     "MIN4", "MAD0", "MAD2", "MAD3", "1ACK", "AKD3", "AKDU", "AFFE", "AFF1", "AFF2",
283                     "SSLR", "NPRR", "2RTO", "3RTO", "4RTO", "5RTO", "6RTO", "CBHD", "NBHD", "CONH",
284                     "LFAK", "STMP", "EACK", "ILD0", "ILD1", "ILD2", "ILD3", "ILD4", "RUNT", "NSTP",
285                     "NRTT", "1PTO", "2PTO", "6PTO", "7PTO", "8PTO", "PTOS", "PTOA", "PEB1", "PEB2",
286                     "PVS1", "PAG1", "PAG2", "PSDA", "PLE1", "PLE2", "APTO", "ELDT", "RVCM", "TCID",
287                     "MPTH", "NCMR", "DFER", "NPCO", "BWRE", "BWMX", "BWID", "BWI1", "BWRS", "BWS2",
288                     "BWS3", "BWS4", "BWS5", "BWS6", "BWP0", "BWP1", "BWP2", "BWP3", "BWP4", "BWG4",
289                     "BWG7", "BWG8", "BWS7", "BWM3", "BWM4", "ICW1", "DTOS", "FIDT", "3AFF", "10AF",
290                     "MTUH", "MTUL", "NSLC", "NCHP", "NBPE", "X509", "X59R", "CHID", "VER ", "NONC",
291                     "NONP", "KEXS", "AEAD", "COPT", "CLOP", "ICSL", "MIBS", "MIUS", "ADE ", "IRTT",
292                     "TRTT", "SNI ", "PUBS", "SCID", "ORBT", "PDMD", "PROF", "CCRT", "EXPY", "STTL",
293                     "SFCW", "CFCW", "UAID", "XLCT", "QLVE", "PDP1", "PDP2", "PDP3", "PDP5", "QNZ2",
294                     "MAD", "IGNP", "SRWP", "ROWF", "ROWR", "GSR0", "GSR1", "GSR2", "GSR3", "NRES",
295                     "INVC", "GWCH", "YTCH", "ACH0", "RREJ", "CADR", "ASAD", "SRST", "CIDK", "CIDS",
296                     "RNON", "RSEQ", "PAD ", "EPID", "SNO0", "STK0", "CRT255", "CSCT");
297 }
298