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.INetd;
25 import android.net.InformationElementParcelable;
26 import android.net.Network;
27 import android.net.ProvisioningConfigurationParcelable;
28 import android.net.ScanResultInfoParcelable;
29 import android.net.StaticIpConfiguration;
30 import android.net.apf.ApfCapabilities;
31 import android.net.ip.IIpClient;
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     /**
79      * Builder to create a {@link ProvisioningConfiguration}.
80      */
81     public static class Builder {
82         protected ProvisioningConfiguration mConfig = new ProvisioningConfiguration();
83 
84         /**
85          * Specify that the configuration should not enable IPv4. It is enabled by default.
86          */
withoutIPv4()87         public Builder withoutIPv4() {
88             mConfig.mEnableIPv4 = false;
89             return this;
90         }
91 
92         /**
93          * Specify that the configuration should not enable IPv6. It is enabled by default.
94          */
withoutIPv6()95         public Builder withoutIPv6() {
96             mConfig.mEnableIPv6 = false;
97             return this;
98         }
99 
100         /**
101          * Specify that the configuration should not use a MultinetworkPolicyTracker. It is used
102          * by default.
103          */
withoutMultinetworkPolicyTracker()104         public Builder withoutMultinetworkPolicyTracker() {
105             mConfig.mUsingMultinetworkPolicyTracker = false;
106             return this;
107         }
108 
109         /**
110          * Specify that the configuration should not use a IpReachabilityMonitor. It is used by
111          * default.
112          */
withoutIpReachabilityMonitor()113         public Builder withoutIpReachabilityMonitor() {
114             mConfig.mUsingIpReachabilityMonitor = false;
115             return this;
116         }
117 
118         /**
119          * Identical to {@link #withPreDhcpAction(int)}, using a default timeout.
120          * @see #withPreDhcpAction(int)
121          */
withPreDhcpAction()122         public Builder withPreDhcpAction() {
123             mConfig.mRequestedPreDhcpActionMs = DEFAULT_TIMEOUT_MS;
124             return this;
125         }
126 
127         /**
128          * Specify that {@link IpClientCallbacks#onPreDhcpAction()} should be called. Clients must
129          * call {@link IIpClient#completedPreDhcpAction()} when the callback called. This behavior
130          * is disabled by default.
131          * @param dhcpActionTimeoutMs Timeout for clients to call completedPreDhcpAction().
132          */
withPreDhcpAction(int dhcpActionTimeoutMs)133         public Builder withPreDhcpAction(int dhcpActionTimeoutMs) {
134             mConfig.mRequestedPreDhcpActionMs = dhcpActionTimeoutMs;
135             return this;
136         }
137 
138         /**
139          * Specify that preconnection feature would be enabled. It's not used by default.
140          */
withPreconnection()141         public Builder withPreconnection() {
142             mConfig.mEnablePreconnection = true;
143             return this;
144         }
145 
146         /**
147          * Specify the initial provisioning configuration.
148          */
withInitialConfiguration(InitialConfiguration initialConfig)149         public Builder withInitialConfiguration(InitialConfiguration initialConfig) {
150             mConfig.mInitialConfig = initialConfig;
151             return this;
152         }
153 
154         /**
155          * Specify a static configuration for provisioning.
156          */
withStaticConfiguration(StaticIpConfiguration staticConfig)157         public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
158             mConfig.mStaticIpConfig = staticConfig;
159             return this;
160         }
161 
162         /**
163          * Specify ApfCapabilities.
164          */
withApfCapabilities(ApfCapabilities apfCapabilities)165         public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
166             mConfig.mApfCapabilities = apfCapabilities;
167             return this;
168         }
169 
170         /**
171          * Specify the timeout to use for provisioning.
172          */
withProvisioningTimeoutMs(int timeoutMs)173         public Builder withProvisioningTimeoutMs(int timeoutMs) {
174             mConfig.mProvisioningTimeoutMs = timeoutMs;
175             return this;
176         }
177 
178         /**
179          * Specify that IPv6 address generation should use a random MAC address.
180          */
withRandomMacAddress()181         public Builder withRandomMacAddress() {
182             mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_EUI64;
183             return this;
184         }
185 
186         /**
187          * Specify that IPv6 address generation should use a stable MAC address.
188          */
withStableMacAddress()189         public Builder withStableMacAddress() {
190             mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
191             return this;
192         }
193 
194         /**
195          * Specify the network to use for provisioning.
196          */
withNetwork(Network network)197         public Builder withNetwork(Network network) {
198             mConfig.mNetwork = network;
199             return this;
200         }
201 
202         /**
203          * Specify the display name that the IpClient should use.
204          */
withDisplayName(String displayName)205         public Builder withDisplayName(String displayName) {
206             mConfig.mDisplayName = displayName;
207             return this;
208         }
209 
210         /**
211          * Specify the information elements included in wifi scan result that was obtained
212          * prior to connecting to the access point, if this is a WiFi network.
213          *
214          * <p>The scan result can be used to infer whether the network is metered.
215          */
withScanResultInfo(ScanResultInfo scanResultInfo)216         public Builder withScanResultInfo(ScanResultInfo scanResultInfo) {
217             mConfig.mScanResultInfo = scanResultInfo;
218             return this;
219         }
220 
221         /**
222          * Specify the L2 information(bssid, l2key and cluster) that the IpClient should use.
223          */
withLayer2Information(Layer2Information layer2Info)224         public Builder withLayer2Information(Layer2Information layer2Info) {
225             mConfig.mLayer2Info = layer2Info;
226             return this;
227         }
228 
229         /**
230          * Build the configuration using previously specified parameters.
231          */
build()232         public ProvisioningConfiguration build() {
233             return new ProvisioningConfiguration(mConfig);
234         }
235     }
236 
237     /**
238      * Class wrapper of {@link android.net.wifi.ScanResult} to encapsulate the SSID and
239      * InformationElements fields of ScanResult.
240      */
241     public static class ScanResultInfo {
242         @NonNull
243         private final String mSsid;
244         @NonNull
245         private final String mBssid;
246         @NonNull
247         private final List<InformationElement> mInformationElements;
248 
249        /**
250         * Class wrapper of {@link android.net.wifi.ScanResult.InformationElement} to encapsulate
251         * the specific IE id and payload fields.
252         */
253         public static class InformationElement {
254             private final int mId;
255             @NonNull
256             private final byte[] mPayload;
257 
InformationElement(int id, @NonNull ByteBuffer payload)258             public InformationElement(int id, @NonNull ByteBuffer payload) {
259                 mId = id;
260                 mPayload = convertToByteArray(payload.asReadOnlyBuffer());
261             }
262 
263            /**
264             * Get the element ID of the information element.
265             */
getId()266             public int getId() {
267                 return mId;
268             }
269 
270            /**
271             * Get the specific content of the information element.
272             */
273             @NonNull
getPayload()274             public ByteBuffer getPayload() {
275                 return ByteBuffer.wrap(mPayload).asReadOnlyBuffer();
276             }
277 
278             @Override
equals(Object o)279             public boolean equals(Object o) {
280                 if (o == this) return true;
281                 if (!(o instanceof InformationElement)) return false;
282                 InformationElement other = (InformationElement) o;
283                 return mId == other.mId && Arrays.equals(mPayload, other.mPayload);
284             }
285 
286             @Override
hashCode()287             public int hashCode() {
288                 return Objects.hash(mId, mPayload);
289             }
290 
291             @Override
toString()292             public String toString() {
293                 return "ID: " + mId + ", " + Arrays.toString(mPayload);
294             }
295 
296             /**
297              * Convert this InformationElement to a {@link InformationElementParcelable}.
298              */
toStableParcelable()299             public InformationElementParcelable toStableParcelable() {
300                 final InformationElementParcelable p = new InformationElementParcelable();
301                 p.id = mId;
302                 p.payload = mPayload != null ? mPayload.clone() : null;
303                 return p;
304             }
305 
306             /**
307              * Create an instance of {@link InformationElement} based on the contents of the
308              * specified {@link InformationElementParcelable}.
309              */
310             @Nullable
fromStableParcelable(InformationElementParcelable p)311             public static InformationElement fromStableParcelable(InformationElementParcelable p) {
312                 if (p == null) return null;
313                 return new InformationElement(p.id,
314                         ByteBuffer.wrap(p.payload.clone()).asReadOnlyBuffer());
315             }
316         }
317 
ScanResultInfo(@onNull String ssid, @NonNull String bssid, @NonNull List<InformationElement> informationElements)318         public ScanResultInfo(@NonNull String ssid, @NonNull String bssid,
319                 @NonNull List<InformationElement> informationElements) {
320             Objects.requireNonNull(ssid, "ssid must not be null.");
321             Objects.requireNonNull(bssid, "bssid must not be null.");
322             mSsid = ssid;
323             mBssid = bssid;
324             mInformationElements =
325                     Collections.unmodifiableList(new ArrayList<>(informationElements));
326         }
327 
328         /**
329          * Get the scanned network name.
330          */
331         @NonNull
getSsid()332         public String getSsid() {
333             return mSsid;
334         }
335 
336         /**
337          * Get the address of the access point.
338          */
339         @NonNull
getBssid()340         public String getBssid() {
341             return mBssid;
342         }
343 
344         /**
345          * Get all information elements found in the beacon.
346          */
347         @NonNull
getInformationElements()348         public List<InformationElement> getInformationElements() {
349             return mInformationElements;
350         }
351 
352         @Override
toString()353         public String toString() {
354             StringBuffer str = new StringBuffer();
355             str.append("SSID: ").append(mSsid);
356             str.append(", BSSID: ").append(mBssid);
357             str.append(", Information Elements: {");
358             for (InformationElement ie : mInformationElements) {
359                 str.append("[").append(ie.toString()).append("]");
360             }
361             str.append("}");
362             return str.toString();
363         }
364 
365         @Override
equals(Object o)366         public boolean equals(Object o) {
367             if (o == this) return true;
368             if (!(o instanceof ScanResultInfo)) return false;
369             ScanResultInfo other = (ScanResultInfo) o;
370             return Objects.equals(mSsid, other.mSsid)
371                     && Objects.equals(mBssid, other.mBssid)
372                     && mInformationElements.equals(other.mInformationElements);
373         }
374 
375         @Override
hashCode()376         public int hashCode() {
377             return Objects.hash(mSsid, mBssid, mInformationElements);
378         }
379 
380         /**
381          * Convert this ScanResultInfo to a {@link ScanResultInfoParcelable}.
382          */
toStableParcelable()383         public ScanResultInfoParcelable toStableParcelable() {
384             final ScanResultInfoParcelable p = new ScanResultInfoParcelable();
385             p.ssid = mSsid;
386             p.bssid = mBssid;
387             p.informationElements = toParcelableArray(mInformationElements,
388                     InformationElement::toStableParcelable, InformationElementParcelable.class);
389             return p;
390         }
391 
392         /**
393          * Create an instance of {@link ScanResultInfo} based on the contents of the specified
394          * {@link ScanResultInfoParcelable}.
395          */
fromStableParcelable(ScanResultInfoParcelable p)396         public static ScanResultInfo fromStableParcelable(ScanResultInfoParcelable p) {
397             if (p == null) return null;
398             final List<InformationElement> ies = new ArrayList<InformationElement>();
399             ies.addAll(fromParcelableArray(p.informationElements,
400                     InformationElement::fromStableParcelable));
401             return new ScanResultInfo(p.ssid, p.bssid, ies);
402         }
403 
convertToByteArray(@onNull final ByteBuffer buffer)404         private static byte[] convertToByteArray(@NonNull final ByteBuffer buffer) {
405             final byte[] bytes = new byte[buffer.limit()];
406             final ByteBuffer copy = buffer.asReadOnlyBuffer();
407             try {
408                 copy.position(0);
409                 copy.get(bytes);
410             } catch (BufferUnderflowException e) {
411                 Log.wtf(TAG, "Buffer under flow exception should never happen.");
412             } finally {
413                 return bytes;
414             }
415         }
416     }
417 
418     public boolean mEnableIPv4 = true;
419     public boolean mEnableIPv6 = true;
420     public boolean mEnablePreconnection = false;
421     public boolean mUsingMultinetworkPolicyTracker = true;
422     public boolean mUsingIpReachabilityMonitor = true;
423     public int mRequestedPreDhcpActionMs;
424     public InitialConfiguration mInitialConfig;
425     public StaticIpConfiguration mStaticIpConfig;
426     public ApfCapabilities mApfCapabilities;
427     public int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS;
428     public int mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
429     public Network mNetwork = null;
430     public String mDisplayName = null;
431     public ScanResultInfo mScanResultInfo;
432     public Layer2Information mLayer2Info;
433 
ProvisioningConfiguration()434     public ProvisioningConfiguration() {} // used by Builder
435 
ProvisioningConfiguration(ProvisioningConfiguration other)436     public ProvisioningConfiguration(ProvisioningConfiguration other) {
437         mEnableIPv4 = other.mEnableIPv4;
438         mEnableIPv6 = other.mEnableIPv6;
439         mEnablePreconnection = other.mEnablePreconnection;
440         mUsingMultinetworkPolicyTracker = other.mUsingMultinetworkPolicyTracker;
441         mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor;
442         mRequestedPreDhcpActionMs = other.mRequestedPreDhcpActionMs;
443         mInitialConfig = InitialConfiguration.copy(other.mInitialConfig);
444         mStaticIpConfig = other.mStaticIpConfig == null
445                 ? null
446                 : new StaticIpConfiguration(other.mStaticIpConfig);
447         mApfCapabilities = other.mApfCapabilities;
448         mProvisioningTimeoutMs = other.mProvisioningTimeoutMs;
449         mIPv6AddrGenMode = other.mIPv6AddrGenMode;
450         mNetwork = other.mNetwork;
451         mDisplayName = other.mDisplayName;
452         mScanResultInfo = other.mScanResultInfo;
453         mLayer2Info = other.mLayer2Info;
454     }
455 
456     /**
457      * Create a ProvisioningConfigurationParcelable from this ProvisioningConfiguration.
458      */
toStableParcelable()459     public ProvisioningConfigurationParcelable toStableParcelable() {
460         final ProvisioningConfigurationParcelable p = new ProvisioningConfigurationParcelable();
461         p.enableIPv4 = mEnableIPv4;
462         p.enableIPv6 = mEnableIPv6;
463         p.enablePreconnection = mEnablePreconnection;
464         p.usingMultinetworkPolicyTracker = mUsingMultinetworkPolicyTracker;
465         p.usingIpReachabilityMonitor = mUsingIpReachabilityMonitor;
466         p.requestedPreDhcpActionMs = mRequestedPreDhcpActionMs;
467         p.initialConfig = mInitialConfig == null ? null : mInitialConfig.toStableParcelable();
468         p.staticIpConfig = mStaticIpConfig == null
469                 ? null
470                 : new StaticIpConfiguration(mStaticIpConfig);
471         p.apfCapabilities = mApfCapabilities; // ApfCapabilities is immutable
472         p.provisioningTimeoutMs = mProvisioningTimeoutMs;
473         p.ipv6AddrGenMode = mIPv6AddrGenMode;
474         p.network = mNetwork;
475         p.displayName = mDisplayName;
476         p.scanResultInfo = mScanResultInfo == null ? null : mScanResultInfo.toStableParcelable();
477         p.layer2Info = mLayer2Info == null ? null : mLayer2Info.toStableParcelable();
478         return p;
479     }
480 
481     /**
482      * Create a ProvisioningConfiguration from a ProvisioningConfigurationParcelable.
483      */
fromStableParcelable( @ullable ProvisioningConfigurationParcelable p)484     public static ProvisioningConfiguration fromStableParcelable(
485             @Nullable ProvisioningConfigurationParcelable p) {
486         if (p == null) return null;
487         final ProvisioningConfiguration config = new ProvisioningConfiguration();
488         config.mEnableIPv4 = p.enableIPv4;
489         config.mEnableIPv6 = p.enableIPv6;
490         config.mEnablePreconnection = p.enablePreconnection;
491         config.mUsingMultinetworkPolicyTracker = p.usingMultinetworkPolicyTracker;
492         config.mUsingIpReachabilityMonitor = p.usingIpReachabilityMonitor;
493         config.mRequestedPreDhcpActionMs = p.requestedPreDhcpActionMs;
494         config.mInitialConfig = InitialConfiguration.fromStableParcelable(p.initialConfig);
495         config.mStaticIpConfig = p.staticIpConfig == null
496                 ? null
497                 : new StaticIpConfiguration(p.staticIpConfig);
498         config.mApfCapabilities = p.apfCapabilities; // ApfCapabilities is immutable
499         config.mProvisioningTimeoutMs = p.provisioningTimeoutMs;
500         config.mIPv6AddrGenMode = p.ipv6AddrGenMode;
501         config.mNetwork = p.network;
502         config.mDisplayName = p.displayName;
503         config.mScanResultInfo = ScanResultInfo.fromStableParcelable(p.scanResultInfo);
504         config.mLayer2Info = Layer2Information.fromStableParcelable(p.layer2Info);
505         return config;
506     }
507 
508     @Override
toString()509     public String toString() {
510         return new StringJoiner(", ", getClass().getSimpleName() + "{", "}")
511                 .add("mEnableIPv4: " + mEnableIPv4)
512                 .add("mEnableIPv6: " + mEnableIPv6)
513                 .add("mEnablePreconnection: " + mEnablePreconnection)
514                 .add("mUsingMultinetworkPolicyTracker: " + mUsingMultinetworkPolicyTracker)
515                 .add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor)
516                 .add("mRequestedPreDhcpActionMs: " + mRequestedPreDhcpActionMs)
517                 .add("mInitialConfig: " + mInitialConfig)
518                 .add("mStaticIpConfig: " + mStaticIpConfig)
519                 .add("mApfCapabilities: " + mApfCapabilities)
520                 .add("mProvisioningTimeoutMs: " + mProvisioningTimeoutMs)
521                 .add("mIPv6AddrGenMode: " + mIPv6AddrGenMode)
522                 .add("mNetwork: " + mNetwork)
523                 .add("mDisplayName: " + mDisplayName)
524                 .add("mScanResultInfo: " + mScanResultInfo)
525                 .add("mLayer2Info: " + mLayer2Info)
526                 .toString();
527     }
528 
529     @Override
equals(Object obj)530     public boolean equals(Object obj) {
531         if (!(obj instanceof ProvisioningConfiguration)) return false;
532         final ProvisioningConfiguration other = (ProvisioningConfiguration) obj;
533         return mEnableIPv4 == other.mEnableIPv4
534                 && mEnableIPv6 == other.mEnableIPv6
535                 && mEnablePreconnection == other.mEnablePreconnection
536                 && mUsingMultinetworkPolicyTracker == other.mUsingMultinetworkPolicyTracker
537                 && mUsingIpReachabilityMonitor == other.mUsingIpReachabilityMonitor
538                 && mRequestedPreDhcpActionMs == other.mRequestedPreDhcpActionMs
539                 && Objects.equals(mInitialConfig, other.mInitialConfig)
540                 && Objects.equals(mStaticIpConfig, other.mStaticIpConfig)
541                 && Objects.equals(mApfCapabilities, other.mApfCapabilities)
542                 && mProvisioningTimeoutMs == other.mProvisioningTimeoutMs
543                 && mIPv6AddrGenMode == other.mIPv6AddrGenMode
544                 && Objects.equals(mNetwork, other.mNetwork)
545                 && Objects.equals(mDisplayName, other.mDisplayName)
546                 && Objects.equals(mScanResultInfo, other.mScanResultInfo)
547                 && Objects.equals(mLayer2Info, other.mLayer2Info);
548     }
549 
isValid()550     public boolean isValid() {
551         return (mInitialConfig == null) || mInitialConfig.isValid();
552     }
553 }
554