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.impl; 6 7 import static org.chromium.net.impl.HttpEngineNativeProvider.EXT_API_LEVEL; 8 import static org.chromium.net.impl.HttpEngineNativeProvider.EXT_VERSION; 9 10 import android.net.http.HttpEngine; 11 import android.util.Log; 12 13 import androidx.annotation.RequiresExtension; 14 import androidx.annotation.VisibleForTesting; 15 16 import org.chromium.net.CronetEngine; 17 import org.chromium.net.ExperimentalCronetEngine; 18 import org.chromium.net.ICronetEngineBuilder; 19 import org.chromium.net.telemetry.ExperimentalOptions; 20 import org.chromium.net.telemetry.OptionalBoolean; 21 22 import java.time.Duration; 23 import java.util.Date; 24 import java.util.Set; 25 26 @RequiresExtension(extension = EXT_API_LEVEL, version = EXT_VERSION) 27 class AndroidHttpEngineBuilderWrapper extends ICronetEngineBuilder { 28 private static final String TAG = "HttpEngBuilderWrap"; 29 30 private final HttpEngine.Builder mBackend; 31 AndroidHttpEngineBuilderWrapper(HttpEngine.Builder backend)32 public AndroidHttpEngineBuilderWrapper(HttpEngine.Builder backend) { 33 this.mBackend = backend; 34 } 35 36 @Override getDefaultUserAgent()37 public String getDefaultUserAgent() { 38 return mBackend.getDefaultUserAgent(); 39 } 40 41 @Override setUserAgent(String userAgent)42 public ICronetEngineBuilder setUserAgent(String userAgent) { 43 mBackend.setUserAgent(userAgent); 44 return this; 45 } 46 47 @Override setStoragePath(String value)48 public ICronetEngineBuilder setStoragePath(String value) { 49 mBackend.setStoragePath(value); 50 return this; 51 } 52 53 @Override setLibraryLoader(CronetEngine.Builder.LibraryLoader loader)54 public ICronetEngineBuilder setLibraryLoader(CronetEngine.Builder.LibraryLoader loader) { 55 Log.w( 56 TAG, 57 "Custom library loader isn't supported when using the platform Cronet provider." 58 + " Ignoring..."); 59 return this; 60 } 61 62 @Override enableQuic(boolean value)63 public ICronetEngineBuilder enableQuic(boolean value) { 64 mBackend.setEnableQuic(value); 65 return this; 66 } 67 68 @Override enableSdch(boolean value)69 public ICronetEngineBuilder enableSdch(boolean value) { 70 // Deprecated and unused by upper layers, do nothing. 71 return this; 72 } 73 74 @Override enableHttp2(boolean value)75 public ICronetEngineBuilder enableHttp2(boolean value) { 76 mBackend.setEnableHttp2(value); 77 return this; 78 } 79 80 @Override enableBrotli(boolean value)81 public ICronetEngineBuilder enableBrotli(boolean value) { 82 mBackend.setEnableBrotli(value); 83 return this; 84 } 85 86 @Override enableHttpCache(int cacheMode, long maxSize)87 public ICronetEngineBuilder enableHttpCache(int cacheMode, long maxSize) { 88 mBackend.setEnableHttpCache(cacheMode, maxSize); 89 return this; 90 } 91 92 @Override addQuicHint(String host, int port, int alternatePort)93 public ICronetEngineBuilder addQuicHint(String host, int port, int alternatePort) { 94 mBackend.addQuicHint(host, port, alternatePort); 95 return this; 96 } 97 98 @Override addPublicKeyPins( String hostName, Set<byte[]> pinsSha256, boolean includeSubdomains, Date expirationDate)99 public ICronetEngineBuilder addPublicKeyPins( 100 String hostName, 101 Set<byte[]> pinsSha256, 102 boolean includeSubdomains, 103 Date expirationDate) { 104 mBackend.addPublicKeyPins( 105 hostName, pinsSha256, includeSubdomains, expirationDate.toInstant()); 106 return this; 107 } 108 109 @Override enablePublicKeyPinningBypassForLocalTrustAnchors(boolean value)110 public ICronetEngineBuilder enablePublicKeyPinningBypassForLocalTrustAnchors(boolean value) { 111 mBackend.setEnablePublicKeyPinningBypassForLocalTrustAnchors(value); 112 return this; 113 } 114 115 @Override setExperimentalOptions(String stringOptions)116 public ICronetEngineBuilder setExperimentalOptions(String stringOptions) { 117 // This only translates known experimental options 118 ExperimentalOptions options = new ExperimentalOptions(stringOptions); 119 mBackend.setConnectionMigrationOptions(parseConnectionMigrationOptions(options)); 120 mBackend.setDnsOptions(parseDnsOptions(options)); 121 mBackend.setQuicOptions(parseQuicOptions(options)); 122 return this; 123 } 124 125 /** 126 * Build a {@link CronetEngine} using this builder's configuration. 127 * 128 * @return constructed {@link CronetEngine}. 129 */ 130 @Override build()131 public ExperimentalCronetEngine build() { 132 return new AndroidHttpEngineWrapper(mBackend.build()); 133 } 134 135 @VisibleForTesting parseConnectionMigrationOptions( ExperimentalOptions options)136 public static android.net.http.ConnectionMigrationOptions parseConnectionMigrationOptions( 137 ExperimentalOptions options) { 138 android.net.http.ConnectionMigrationOptions.Builder cmOptionsBuilder = 139 new android.net.http.ConnectionMigrationOptions.Builder(); 140 141 cmOptionsBuilder.setDefaultNetworkMigration( 142 optionalBooleanToMigrationOptionState( 143 options.getMigrateSessionsOnNetworkChangeV2Option())); 144 cmOptionsBuilder.setPathDegradationMigration( 145 optionalBooleanToMigrationOptionState(options.getAllowPortMigration())); 146 147 OptionalBoolean migrateSessionsEarly = options.getMigrateSessionsEarlyV2(); 148 cmOptionsBuilder.setAllowNonDefaultNetworkUsage( 149 optionalBooleanToMigrationOptionState(migrateSessionsEarly)); 150 if (migrateSessionsEarly == OptionalBoolean.TRUE) { 151 cmOptionsBuilder.setPathDegradationMigration( 152 optionalBooleanToMigrationOptionState(OptionalBoolean.TRUE)); 153 } 154 155 return cmOptionsBuilder.build(); 156 } 157 158 @VisibleForTesting parseDnsOptions(ExperimentalOptions options)159 public static android.net.http.DnsOptions parseDnsOptions(ExperimentalOptions options) { 160 android.net.http.DnsOptions.StaleDnsOptions.Builder staleDnsOptionBuilder = 161 new android.net.http.DnsOptions.StaleDnsOptions.Builder(); 162 int staleDnsDelay = options.getStaleDnsDelayMillisOption(); 163 if (staleDnsDelay != ExperimentalOptions.UNSET_INT_VALUE) { 164 staleDnsOptionBuilder.setFreshLookupTimeout(Duration.ofMillis(staleDnsDelay)); 165 } 166 167 int expiredDelay = options.getStaleDnsMaxExpiredTimeMillisOption(); 168 if (expiredDelay != ExperimentalOptions.UNSET_INT_VALUE) { 169 staleDnsOptionBuilder.setMaxExpiredDelay(Duration.ofMillis(expiredDelay)); 170 } 171 172 staleDnsOptionBuilder 173 .setAllowCrossNetworkUsage( 174 optionalBooleanToMigrationOptionState( 175 options.getStaleDnsAllowOtherNetworkOption())) 176 .setUseStaleOnNameNotResolved( 177 optionalBooleanToMigrationOptionState( 178 options.getStaleDnsUseStaleOnNameNotResolvedOption())); 179 180 android.net.http.DnsOptions.Builder dnsOptionsBuilder = 181 new android.net.http.DnsOptions.Builder(); 182 dnsOptionsBuilder 183 .setUseHttpStackDnsResolver( 184 optionalBooleanToMigrationOptionState(options.getAsyncDnsEnableOption())) 185 .setStaleDns( 186 optionalBooleanToMigrationOptionState(options.getStaleDnsEnableOption())) 187 .setStaleDnsOptions(staleDnsOptionBuilder.build()) 188 .setPreestablishConnectionsToStaleDnsResults( 189 optionalBooleanToMigrationOptionState( 190 options.getRaceStaleDnsOnConnection())) 191 .setPersistHostCache( 192 optionalBooleanToMigrationOptionState( 193 options.getStaleDnsPersistToDiskOption())); 194 int persistHostCachePeriod = options.getStaleDnsPersistDelayMillisOption(); 195 if (persistHostCachePeriod != ExperimentalOptions.UNSET_INT_VALUE) { 196 dnsOptionsBuilder.setPersistHostCachePeriod(Duration.ofMillis(persistHostCachePeriod)); 197 } 198 199 return dnsOptionsBuilder.build(); 200 } 201 202 @VisibleForTesting parseQuicOptions(ExperimentalOptions options)203 public static android.net.http.QuicOptions parseQuicOptions(ExperimentalOptions options) { 204 android.net.http.QuicOptions.Builder quicOptionsBuilder = 205 new android.net.http.QuicOptions.Builder(); 206 207 if (options.getHostWhitelist() != null) { 208 for (String host : options.getHostWhitelist().split(",")) { 209 quicOptionsBuilder.addAllowedQuicHost(host); 210 } 211 } 212 213 int inMemoryServerConfigsCacheSize = options.getMaxServerConfigsStoredInPropertiesOption(); 214 if (inMemoryServerConfigsCacheSize != ExperimentalOptions.UNSET_INT_VALUE) { 215 quicOptionsBuilder.setInMemoryServerConfigsCacheSize(inMemoryServerConfigsCacheSize); 216 } 217 218 String handshakeUserAgent = options.getUserAgentId(); 219 if (handshakeUserAgent != null) { 220 quicOptionsBuilder.setHandshakeUserAgent(handshakeUserAgent); 221 } 222 223 int idleConnectionTimeoutSeconds = options.getIdleConnectionTimeoutSecondsOption(); 224 if (idleConnectionTimeoutSeconds != ExperimentalOptions.UNSET_INT_VALUE) { 225 quicOptionsBuilder.setIdleConnectionTimeout( 226 Duration.ofSeconds(idleConnectionTimeoutSeconds)); 227 } 228 229 return quicOptionsBuilder.build(); 230 } 231 232 /** 233 * HttpEngine XOptions exposes X_OPTION_* IntDefs that map to the same integer values. To 234 * simplify the code, we are reusing ConnectionMigrationOptions.MIGRATION_OPTION_* for 235 * DnsOptions and QuicOptions. 236 */ optionalBooleanToMigrationOptionState(OptionalBoolean value)237 private static int optionalBooleanToMigrationOptionState(OptionalBoolean value) { 238 switch (value) { 239 case TRUE: 240 return android.net.http.ConnectionMigrationOptions.MIGRATION_OPTION_ENABLED; 241 case FALSE: 242 return android.net.http.ConnectionMigrationOptions.MIGRATION_OPTION_DISABLED; 243 case UNSET: 244 return android.net.http.ConnectionMigrationOptions.MIGRATION_OPTION_UNSPECIFIED; 245 } 246 247 throw new AssertionError("Invalid OptionalBoolean value: " + value); 248 } 249 } 250