1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.net.shared; 18 19 import static android.net.shared.ParcelableUtil.fromParcelableArray; 20 import static android.net.shared.ParcelableUtil.toParcelableArray; 21 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.net.InformationElementParcelable; 25 import android.net.Network; 26 import android.net.ProvisioningConfigurationParcelable; 27 import android.net.ScanResultInfoParcelable; 28 import android.net.StaticIpConfiguration; 29 import android.net.apf.ApfCapabilities; 30 import android.net.ip.IIpClient; 31 import android.net.networkstack.aidl.dhcp.DhcpOption; 32 import android.util.Log; 33 34 import java.nio.BufferUnderflowException; 35 import java.nio.ByteBuffer; 36 import java.util.ArrayList; 37 import java.util.Arrays; 38 import java.util.Collections; 39 import java.util.List; 40 import java.util.Objects; 41 import java.util.StringJoiner; 42 43 /** 44 * This class encapsulates parameters to be passed to 45 * IpClient#startProvisioning(). A defensive copy is made by IpClient 46 * and the values specified herein are in force until IpClient#stop() 47 * is called. 48 * 49 * Example use: 50 * 51 * final ProvisioningConfiguration config = 52 * new ProvisioningConfiguration.Builder() 53 * .withPreDhcpAction() 54 * .withProvisioningTimeoutMs(36 * 1000) 55 * .build(); 56 * mIpClient.startProvisioning(config.toStableParcelable()); 57 * ... 58 * mIpClient.stop(); 59 * 60 * The specified provisioning configuration will only be active until 61 * IIpClient#stop() is called. Future calls to IIpClient#startProvisioning() 62 * must specify the configuration again. 63 * @hide 64 */ 65 public class ProvisioningConfiguration { 66 private static final String TAG = "ProvisioningConfiguration"; 67 68 // TODO: Delete this default timeout once those callers that care are 69 // fixed to pass in their preferred timeout. 70 // 71 // We pick 18 seconds so we can send DHCP requests at 72 // 73 // t=0, t=1, t=3, t=7, t=16 74 // 75 // allowing for 10% jitter. 76 private static final int DEFAULT_TIMEOUT_MS = 18 * 1000; 77 78 // TODO: These cannot be imported from INetd.aidl, because networkstack-client cannot depend on 79 // INetd, as there are users of IpClient that depend on INetd directly (potentially at a 80 // different version, which is not allowed by the build system). 81 // Find a better way to express these constants. 82 public static final int IPV6_ADDR_GEN_MODE_EUI64 = 0; 83 public static final int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2; 84 85 /** 86 * Builder to create a {@link ProvisioningConfiguration}. 87 */ 88 public static class Builder { 89 protected ProvisioningConfiguration mConfig = new ProvisioningConfiguration(); 90 91 /** 92 * Specify that the configuration should not enable IPv4. It is enabled by default. 93 */ withoutIPv4()94 public Builder withoutIPv4() { 95 mConfig.mEnableIPv4 = false; 96 return this; 97 } 98 99 /** 100 * Specify that the configuration should not enable IPv6. It is enabled by default. 101 */ withoutIPv6()102 public Builder withoutIPv6() { 103 mConfig.mEnableIPv6 = false; 104 return this; 105 } 106 107 /** 108 * Specify that the configuration should not use a MultinetworkPolicyTracker. It is used 109 * by default. 110 */ withoutMultinetworkPolicyTracker()111 public Builder withoutMultinetworkPolicyTracker() { 112 mConfig.mUsingMultinetworkPolicyTracker = false; 113 return this; 114 } 115 116 /** 117 * Specify that the configuration should not use a IpReachabilityMonitor. It is used by 118 * default. 119 */ withoutIpReachabilityMonitor()120 public Builder withoutIpReachabilityMonitor() { 121 mConfig.mUsingIpReachabilityMonitor = false; 122 return this; 123 } 124 125 /** 126 * Identical to {@link #withPreDhcpAction(int)}, using a default timeout. 127 * @see #withPreDhcpAction(int) 128 */ withPreDhcpAction()129 public Builder withPreDhcpAction() { 130 mConfig.mRequestedPreDhcpActionMs = DEFAULT_TIMEOUT_MS; 131 return this; 132 } 133 134 /** 135 * Specify that {@link IpClientCallbacks#onPreDhcpAction()} should be called. Clients must 136 * call {@link IIpClient#completedPreDhcpAction()} when the callback called. This behavior 137 * is disabled by default. 138 * @param dhcpActionTimeoutMs Timeout for clients to call completedPreDhcpAction(). 139 */ withPreDhcpAction(int dhcpActionTimeoutMs)140 public Builder withPreDhcpAction(int dhcpActionTimeoutMs) { 141 mConfig.mRequestedPreDhcpActionMs = dhcpActionTimeoutMs; 142 return this; 143 } 144 145 /** 146 * Specify that preconnection feature would be enabled. It's not used by default. 147 */ withPreconnection()148 public Builder withPreconnection() { 149 mConfig.mEnablePreconnection = true; 150 return this; 151 } 152 153 /** 154 * Specify the initial provisioning configuration. 155 */ withInitialConfiguration(InitialConfiguration initialConfig)156 public Builder withInitialConfiguration(InitialConfiguration initialConfig) { 157 mConfig.mInitialConfig = initialConfig; 158 return this; 159 } 160 161 /** 162 * Specify a static configuration for provisioning. 163 */ withStaticConfiguration(StaticIpConfiguration staticConfig)164 public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) { 165 mConfig.mStaticIpConfig = staticConfig; 166 return this; 167 } 168 169 /** 170 * Specify ApfCapabilities. 171 */ withApfCapabilities(ApfCapabilities apfCapabilities)172 public Builder withApfCapabilities(ApfCapabilities apfCapabilities) { 173 mConfig.mApfCapabilities = apfCapabilities; 174 return this; 175 } 176 177 /** 178 * Specify the timeout to use for provisioning. 179 */ withProvisioningTimeoutMs(int timeoutMs)180 public Builder withProvisioningTimeoutMs(int timeoutMs) { 181 mConfig.mProvisioningTimeoutMs = timeoutMs; 182 return this; 183 } 184 185 /** 186 * Specify that IPv6 address generation should use a random MAC address. 187 */ withRandomMacAddress()188 public Builder withRandomMacAddress() { 189 mConfig.mIPv6AddrGenMode = IPV6_ADDR_GEN_MODE_EUI64; 190 return this; 191 } 192 193 /** 194 * Specify that IPv6 address generation should use a stable MAC address. 195 */ withStableMacAddress()196 public Builder withStableMacAddress() { 197 mConfig.mIPv6AddrGenMode = IPV6_ADDR_GEN_MODE_STABLE_PRIVACY; 198 return this; 199 } 200 201 /** 202 * Specify the network to use for provisioning. 203 */ withNetwork(Network network)204 public Builder withNetwork(Network network) { 205 mConfig.mNetwork = network; 206 return this; 207 } 208 209 /** 210 * Specify the display name that the IpClient should use. 211 */ withDisplayName(String displayName)212 public Builder withDisplayName(String displayName) { 213 mConfig.mDisplayName = displayName; 214 return this; 215 } 216 217 /** 218 * Specify the information elements included in wifi scan result that was obtained 219 * prior to connecting to the access point, if this is a WiFi network. 220 * 221 * <p>The scan result can be used to infer whether the network is metered. 222 */ withScanResultInfo(ScanResultInfo scanResultInfo)223 public Builder withScanResultInfo(ScanResultInfo scanResultInfo) { 224 mConfig.mScanResultInfo = scanResultInfo; 225 return this; 226 } 227 228 /** 229 * Specify the L2 information(bssid, l2key and cluster) that the IpClient should use. 230 */ withLayer2Information(Layer2Information layer2Info)231 public Builder withLayer2Information(Layer2Information layer2Info) { 232 mConfig.mLayer2Info = layer2Info; 233 return this; 234 } 235 236 /** 237 * Specify the customized DHCP options to be put in the PRL or in the DHCP packet. Options 238 * with null value will be put in the PRL. 239 * 240 * @param: options customized DHCP option stable parcelable list. 241 */ withDhcpOptions(List<DhcpOption> options)242 public Builder withDhcpOptions(List<DhcpOption> options) { 243 mConfig.mDhcpOptions = options; 244 return this; 245 } 246 247 /** 248 * Build the configuration using previously specified parameters. 249 */ build()250 public ProvisioningConfiguration build() { 251 return new ProvisioningConfiguration(mConfig); 252 } 253 } 254 255 /** 256 * Class wrapper of {@link android.net.wifi.ScanResult} to encapsulate the SSID and 257 * InformationElements fields of ScanResult. 258 */ 259 public static class ScanResultInfo { 260 @NonNull 261 private final String mSsid; 262 @NonNull 263 private final String mBssid; 264 @NonNull 265 private final List<InformationElement> mInformationElements; 266 267 /** 268 * Class wrapper of {@link android.net.wifi.ScanResult.InformationElement} to encapsulate 269 * the specific IE id and payload fields. 270 */ 271 public static class InformationElement { 272 private final int mId; 273 @NonNull 274 private final byte[] mPayload; 275 InformationElement(int id, @NonNull ByteBuffer payload)276 public InformationElement(int id, @NonNull ByteBuffer payload) { 277 mId = id; 278 mPayload = convertToByteArray(payload.asReadOnlyBuffer()); 279 } 280 281 /** 282 * Get the element ID of the information element. 283 */ getId()284 public int getId() { 285 return mId; 286 } 287 288 /** 289 * Get the specific content of the information element. 290 */ 291 @NonNull getPayload()292 public ByteBuffer getPayload() { 293 return ByteBuffer.wrap(mPayload).asReadOnlyBuffer(); 294 } 295 296 @Override equals(Object o)297 public boolean equals(Object o) { 298 if (o == this) return true; 299 if (!(o instanceof InformationElement)) return false; 300 InformationElement other = (InformationElement) o; 301 return mId == other.mId && Arrays.equals(mPayload, other.mPayload); 302 } 303 304 @Override hashCode()305 public int hashCode() { 306 return Objects.hash(mId, mPayload); 307 } 308 309 @Override toString()310 public String toString() { 311 return "ID: " + mId + ", " + Arrays.toString(mPayload); 312 } 313 314 /** 315 * Convert this InformationElement to a {@link InformationElementParcelable}. 316 */ toStableParcelable()317 public InformationElementParcelable toStableParcelable() { 318 final InformationElementParcelable p = new InformationElementParcelable(); 319 p.id = mId; 320 p.payload = mPayload != null ? mPayload.clone() : null; 321 return p; 322 } 323 324 /** 325 * Create an instance of {@link InformationElement} based on the contents of the 326 * specified {@link InformationElementParcelable}. 327 */ 328 @Nullable fromStableParcelable(InformationElementParcelable p)329 public static InformationElement fromStableParcelable(InformationElementParcelable p) { 330 if (p == null) return null; 331 return new InformationElement(p.id, 332 ByteBuffer.wrap(p.payload.clone()).asReadOnlyBuffer()); 333 } 334 } 335 ScanResultInfo(@onNull String ssid, @NonNull String bssid, @NonNull List<InformationElement> informationElements)336 public ScanResultInfo(@NonNull String ssid, @NonNull String bssid, 337 @NonNull List<InformationElement> informationElements) { 338 Objects.requireNonNull(ssid, "ssid must not be null."); 339 Objects.requireNonNull(bssid, "bssid must not be null."); 340 mSsid = ssid; 341 mBssid = bssid; 342 mInformationElements = 343 Collections.unmodifiableList(new ArrayList<>(informationElements)); 344 } 345 346 /** 347 * Get the scanned network name. 348 */ 349 @NonNull getSsid()350 public String getSsid() { 351 return mSsid; 352 } 353 354 /** 355 * Get the address of the access point. 356 */ 357 @NonNull getBssid()358 public String getBssid() { 359 return mBssid; 360 } 361 362 /** 363 * Get all information elements found in the beacon. 364 */ 365 @NonNull getInformationElements()366 public List<InformationElement> getInformationElements() { 367 return mInformationElements; 368 } 369 370 @Override toString()371 public String toString() { 372 StringBuffer str = new StringBuffer(); 373 str.append("SSID: ").append(mSsid); 374 str.append(", BSSID: ").append(mBssid); 375 str.append(", Information Elements: {"); 376 for (InformationElement ie : mInformationElements) { 377 str.append("[").append(ie.toString()).append("]"); 378 } 379 str.append("}"); 380 return str.toString(); 381 } 382 383 @Override equals(Object o)384 public boolean equals(Object o) { 385 if (o == this) return true; 386 if (!(o instanceof ScanResultInfo)) return false; 387 ScanResultInfo other = (ScanResultInfo) o; 388 return Objects.equals(mSsid, other.mSsid) 389 && Objects.equals(mBssid, other.mBssid) 390 && mInformationElements.equals(other.mInformationElements); 391 } 392 393 @Override hashCode()394 public int hashCode() { 395 return Objects.hash(mSsid, mBssid, mInformationElements); 396 } 397 398 /** 399 * Convert this ScanResultInfo to a {@link ScanResultInfoParcelable}. 400 */ toStableParcelable()401 public ScanResultInfoParcelable toStableParcelable() { 402 final ScanResultInfoParcelable p = new ScanResultInfoParcelable(); 403 p.ssid = mSsid; 404 p.bssid = mBssid; 405 p.informationElements = toParcelableArray(mInformationElements, 406 InformationElement::toStableParcelable, InformationElementParcelable.class); 407 return p; 408 } 409 410 /** 411 * Create an instance of {@link ScanResultInfo} based on the contents of the specified 412 * {@link ScanResultInfoParcelable}. 413 */ fromStableParcelable(ScanResultInfoParcelable p)414 public static ScanResultInfo fromStableParcelable(ScanResultInfoParcelable p) { 415 if (p == null) return null; 416 final List<InformationElement> ies = new ArrayList<InformationElement>(); 417 ies.addAll(fromParcelableArray(p.informationElements, 418 InformationElement::fromStableParcelable)); 419 return new ScanResultInfo(p.ssid, p.bssid, ies); 420 } 421 convertToByteArray(@onNull final ByteBuffer buffer)422 private static byte[] convertToByteArray(@NonNull final ByteBuffer buffer) { 423 final byte[] bytes = new byte[buffer.limit()]; 424 final ByteBuffer copy = buffer.asReadOnlyBuffer(); 425 try { 426 copy.position(0); 427 copy.get(bytes); 428 } catch (BufferUnderflowException e) { 429 Log.wtf(TAG, "Buffer under flow exception should never happen."); 430 } finally { 431 return bytes; 432 } 433 } 434 } 435 436 public boolean mEnableIPv4 = true; 437 public boolean mEnableIPv6 = true; 438 public boolean mEnablePreconnection = false; 439 public boolean mUsingMultinetworkPolicyTracker = true; 440 public boolean mUsingIpReachabilityMonitor = true; 441 public int mRequestedPreDhcpActionMs; 442 public InitialConfiguration mInitialConfig; 443 public StaticIpConfiguration mStaticIpConfig; 444 public ApfCapabilities mApfCapabilities; 445 public int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS; 446 public int mIPv6AddrGenMode = IPV6_ADDR_GEN_MODE_STABLE_PRIVACY; 447 public Network mNetwork = null; 448 public String mDisplayName = null; 449 public ScanResultInfo mScanResultInfo; 450 public Layer2Information mLayer2Info; 451 public List<DhcpOption> mDhcpOptions; 452 ProvisioningConfiguration()453 public ProvisioningConfiguration() {} // used by Builder 454 ProvisioningConfiguration(ProvisioningConfiguration other)455 public ProvisioningConfiguration(ProvisioningConfiguration other) { 456 mEnableIPv4 = other.mEnableIPv4; 457 mEnableIPv6 = other.mEnableIPv6; 458 mEnablePreconnection = other.mEnablePreconnection; 459 mUsingMultinetworkPolicyTracker = other.mUsingMultinetworkPolicyTracker; 460 mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor; 461 mRequestedPreDhcpActionMs = other.mRequestedPreDhcpActionMs; 462 mInitialConfig = InitialConfiguration.copy(other.mInitialConfig); 463 mStaticIpConfig = other.mStaticIpConfig == null 464 ? null 465 : new StaticIpConfiguration(other.mStaticIpConfig); 466 mApfCapabilities = other.mApfCapabilities; 467 mProvisioningTimeoutMs = other.mProvisioningTimeoutMs; 468 mIPv6AddrGenMode = other.mIPv6AddrGenMode; 469 mNetwork = other.mNetwork; 470 mDisplayName = other.mDisplayName; 471 mScanResultInfo = other.mScanResultInfo; 472 mLayer2Info = other.mLayer2Info; 473 mDhcpOptions = other.mDhcpOptions; 474 } 475 476 /** 477 * Create a ProvisioningConfigurationParcelable from this ProvisioningConfiguration. 478 */ toStableParcelable()479 public ProvisioningConfigurationParcelable toStableParcelable() { 480 final ProvisioningConfigurationParcelable p = new ProvisioningConfigurationParcelable(); 481 p.enableIPv4 = mEnableIPv4; 482 p.enableIPv6 = mEnableIPv6; 483 p.enablePreconnection = mEnablePreconnection; 484 p.usingMultinetworkPolicyTracker = mUsingMultinetworkPolicyTracker; 485 p.usingIpReachabilityMonitor = mUsingIpReachabilityMonitor; 486 p.requestedPreDhcpActionMs = mRequestedPreDhcpActionMs; 487 p.initialConfig = (mInitialConfig == null) ? null : mInitialConfig.toStableParcelable(); 488 p.staticIpConfig = (mStaticIpConfig == null) 489 ? null 490 : new StaticIpConfiguration(mStaticIpConfig); 491 p.apfCapabilities = mApfCapabilities; // ApfCapabilities is immutable 492 p.provisioningTimeoutMs = mProvisioningTimeoutMs; 493 p.ipv6AddrGenMode = mIPv6AddrGenMode; 494 p.network = mNetwork; 495 p.displayName = mDisplayName; 496 p.scanResultInfo = (mScanResultInfo == null) ? null : mScanResultInfo.toStableParcelable(); 497 p.layer2Info = (mLayer2Info == null) ? null : mLayer2Info.toStableParcelable(); 498 p.options = (mDhcpOptions == null) ? null : new ArrayList<>(mDhcpOptions); 499 return p; 500 } 501 502 /** 503 * Create a ProvisioningConfiguration from a ProvisioningConfigurationParcelable. 504 */ fromStableParcelable( @ullable ProvisioningConfigurationParcelable p)505 public static ProvisioningConfiguration fromStableParcelable( 506 @Nullable ProvisioningConfigurationParcelable p) { 507 if (p == null) return null; 508 final ProvisioningConfiguration config = new ProvisioningConfiguration(); 509 config.mEnableIPv4 = p.enableIPv4; 510 config.mEnableIPv6 = p.enableIPv6; 511 config.mEnablePreconnection = p.enablePreconnection; 512 config.mUsingMultinetworkPolicyTracker = p.usingMultinetworkPolicyTracker; 513 config.mUsingIpReachabilityMonitor = p.usingIpReachabilityMonitor; 514 config.mRequestedPreDhcpActionMs = p.requestedPreDhcpActionMs; 515 config.mInitialConfig = InitialConfiguration.fromStableParcelable(p.initialConfig); 516 config.mStaticIpConfig = (p.staticIpConfig == null) 517 ? null 518 : new StaticIpConfiguration(p.staticIpConfig); 519 config.mApfCapabilities = p.apfCapabilities; // ApfCapabilities is immutable 520 config.mProvisioningTimeoutMs = p.provisioningTimeoutMs; 521 config.mIPv6AddrGenMode = p.ipv6AddrGenMode; 522 config.mNetwork = p.network; 523 config.mDisplayName = p.displayName; 524 config.mScanResultInfo = ScanResultInfo.fromStableParcelable(p.scanResultInfo); 525 config.mLayer2Info = Layer2Information.fromStableParcelable(p.layer2Info); 526 config.mDhcpOptions = (p.options == null) ? null : new ArrayList<>(p.options); 527 return config; 528 } 529 530 @Override toString()531 public String toString() { 532 return new StringJoiner(", ", getClass().getSimpleName() + "{", "}") 533 .add("mEnableIPv4: " + mEnableIPv4) 534 .add("mEnableIPv6: " + mEnableIPv6) 535 .add("mEnablePreconnection: " + mEnablePreconnection) 536 .add("mUsingMultinetworkPolicyTracker: " + mUsingMultinetworkPolicyTracker) 537 .add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor) 538 .add("mRequestedPreDhcpActionMs: " + mRequestedPreDhcpActionMs) 539 .add("mInitialConfig: " + mInitialConfig) 540 .add("mStaticIpConfig: " + mStaticIpConfig) 541 .add("mApfCapabilities: " + mApfCapabilities) 542 .add("mProvisioningTimeoutMs: " + mProvisioningTimeoutMs) 543 .add("mIPv6AddrGenMode: " + mIPv6AddrGenMode) 544 .add("mNetwork: " + mNetwork) 545 .add("mDisplayName: " + mDisplayName) 546 .add("mScanResultInfo: " + mScanResultInfo) 547 .add("mLayer2Info: " + mLayer2Info) 548 .add("mDhcpOptions: " + mDhcpOptions) 549 .toString(); 550 } 551 552 // TODO: mark DhcpOption stable parcelable with @JavaDerive(equals=true, toString=true) 553 // and @JavaOnlyImmutable. dhcpOptionEquals(@ullable DhcpOption obj1, @Nullable DhcpOption obj2)554 private static boolean dhcpOptionEquals(@Nullable DhcpOption obj1, @Nullable DhcpOption obj2) { 555 if (obj1 == obj2) return true; 556 if (obj1 == null || obj2 == null) return false; 557 return obj1.type == obj2.type && Arrays.equals(obj1.value, obj2.value); 558 } 559 560 // TODO: use Objects.equals(List<DhcpOption>, List<DhcpOption>) method instead once 561 // auto-generated equals() method of stable parcelable is supported in mainline-prod. dhcpOptionListEquals(@ullable List<DhcpOption> l1, @Nullable List<DhcpOption> l2)562 private static boolean dhcpOptionListEquals(@Nullable List<DhcpOption> l1, 563 @Nullable List<DhcpOption> l2) { 564 if (l1 == l2) return true; 565 if (l1 == null || l2 == null) return false; 566 if (l1.size() != l2.size()) return false; 567 568 for (int i = 0; i < l1.size(); i++) { 569 if (!dhcpOptionEquals(l1.get(i), l2.get(i))) return false; 570 } 571 return true; 572 } 573 574 @Override equals(Object obj)575 public boolean equals(Object obj) { 576 if (!(obj instanceof ProvisioningConfiguration)) return false; 577 final ProvisioningConfiguration other = (ProvisioningConfiguration) obj; 578 return mEnableIPv4 == other.mEnableIPv4 579 && mEnableIPv6 == other.mEnableIPv6 580 && mEnablePreconnection == other.mEnablePreconnection 581 && mUsingMultinetworkPolicyTracker == other.mUsingMultinetworkPolicyTracker 582 && mUsingIpReachabilityMonitor == other.mUsingIpReachabilityMonitor 583 && mRequestedPreDhcpActionMs == other.mRequestedPreDhcpActionMs 584 && Objects.equals(mInitialConfig, other.mInitialConfig) 585 && Objects.equals(mStaticIpConfig, other.mStaticIpConfig) 586 && Objects.equals(mApfCapabilities, other.mApfCapabilities) 587 && mProvisioningTimeoutMs == other.mProvisioningTimeoutMs 588 && mIPv6AddrGenMode == other.mIPv6AddrGenMode 589 && Objects.equals(mNetwork, other.mNetwork) 590 && Objects.equals(mDisplayName, other.mDisplayName) 591 && Objects.equals(mScanResultInfo, other.mScanResultInfo) 592 && Objects.equals(mLayer2Info, other.mLayer2Info) 593 && dhcpOptionListEquals(mDhcpOptions, other.mDhcpOptions); 594 } 595 isValid()596 public boolean isValid() { 597 return (mInitialConfig == null) || mInitialConfig.isValid(); 598 } 599 } 600