1 /**
2  * Copyright (c) 2017, 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.wifi.hotspot2.pps;
18 
19 import android.os.Parcel;
20 import android.os.Parcelable;
21 import android.text.TextUtils;
22 import android.util.Log;
23 
24 import java.nio.charset.StandardCharsets;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.Collections;
28 import java.util.HashMap;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Objects;
32 
33 /**
34  * Class representing Policy subtree in PerProviderSubscription (PPS)
35  * Management Object (MO) tree.
36  *
37  * The Policy specifies additional criteria for Passpoint network selections, such as preferred
38  * roaming partner, minimum backhaul bandwidth, and etc. It also provides the meta data for
39  * updating the policy.
40  *
41  * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
42  * Release 2 Technical Specification.
43  *
44  * @hide
45  */
46 public final class Policy implements Parcelable {
47     private static final String TAG = "Policy";
48 
49     /**
50      * Maximum number of SSIDs in the exclusion list.
51      */
52     private static final int MAX_EXCLUSION_SSIDS = 128;
53 
54     /**
55      * Maximum byte for SSID.
56      */
57     private static final int MAX_SSID_BYTES = 32;
58 
59     /**
60      * Maximum bytes for port string in {@link #requiredProtoPortMap}.
61      */
62     private static final int MAX_PORT_STRING_BYTES = 64;
63 
64     /**
65      * Integer value used for indicating null value in the Parcel.
66      */
67     private static final int NULL_VALUE = -1;
68 
69     /**
70      * Minimum available downlink/uplink bandwidth (in kilobits per second) required when
71      * selecting a network from home providers.
72      *
73      * The bandwidth is calculated as the LinkSpeed * (1 – LinkLoad/255), where LinkSpeed
74      * and LinkLoad parameters are drawn from the WAN Metrics ANQP element at that hotspot.
75      *
76      * Using Long.MIN_VALUE to indicate unset value.
77      */
78     private long mMinHomeDownlinkBandwidth = Long.MIN_VALUE;
setMinHomeDownlinkBandwidth(long minHomeDownlinkBandwidth)79     public void setMinHomeDownlinkBandwidth(long minHomeDownlinkBandwidth) {
80         mMinHomeDownlinkBandwidth = minHomeDownlinkBandwidth;
81     }
getMinHomeDownlinkBandwidth()82     public long getMinHomeDownlinkBandwidth() {
83         return mMinHomeDownlinkBandwidth;
84     }
85     private long mMinHomeUplinkBandwidth = Long.MIN_VALUE;
setMinHomeUplinkBandwidth(long minHomeUplinkBandwidth)86     public void setMinHomeUplinkBandwidth(long minHomeUplinkBandwidth) {
87         mMinHomeUplinkBandwidth = minHomeUplinkBandwidth;
88     }
getMinHomeUplinkBandwidth()89     public long getMinHomeUplinkBandwidth() {
90         return mMinHomeUplinkBandwidth;
91     }
92 
93     /**
94      * Minimum available downlink/uplink bandwidth (in kilobits per second) required when
95      * selecting a network from roaming providers.
96      *
97      * The bandwidth is calculated as the LinkSpeed * (1 – LinkLoad/255), where LinkSpeed
98      * and LinkLoad parameters are drawn from the WAN Metrics ANQP element at that hotspot.
99      *
100      * Using Long.MIN_VALUE to indicate unset value.
101      */
102     private long mMinRoamingDownlinkBandwidth = Long.MIN_VALUE;
setMinRoamingDownlinkBandwidth(long minRoamingDownlinkBandwidth)103     public void setMinRoamingDownlinkBandwidth(long minRoamingDownlinkBandwidth) {
104         mMinRoamingDownlinkBandwidth = minRoamingDownlinkBandwidth;
105     }
getMinRoamingDownlinkBandwidth()106     public long getMinRoamingDownlinkBandwidth() {
107         return mMinRoamingDownlinkBandwidth;
108     }
109     private long mMinRoamingUplinkBandwidth = Long.MIN_VALUE;
setMinRoamingUplinkBandwidth(long minRoamingUplinkBandwidth)110     public void setMinRoamingUplinkBandwidth(long minRoamingUplinkBandwidth) {
111         mMinRoamingUplinkBandwidth = minRoamingUplinkBandwidth;
112     }
getMinRoamingUplinkBandwidth()113     public long getMinRoamingUplinkBandwidth() {
114         return mMinRoamingUplinkBandwidth;
115     }
116 
117     /**
118      * List of SSIDs that are not preferred by the Home SP.
119      */
120     private String[] mExcludedSsidList = null;
setExcludedSsidList(String[] excludedSsidList)121     public void setExcludedSsidList(String[] excludedSsidList) {
122         mExcludedSsidList = excludedSsidList;
123     }
getExcludedSsidList()124     public String[] getExcludedSsidList() {
125         return mExcludedSsidList;
126     }
127 
128     /**
129      * List of IP protocol and port number required by one or more operator supported application.
130      * The port string contained one or more port numbers delimited by ",".
131      */
132     private Map<Integer, String> mRequiredProtoPortMap = null;
setRequiredProtoPortMap(Map<Integer, String> requiredProtoPortMap)133     public void setRequiredProtoPortMap(Map<Integer, String> requiredProtoPortMap) {
134         mRequiredProtoPortMap = requiredProtoPortMap;
135     }
getRequiredProtoPortMap()136     public Map<Integer, String> getRequiredProtoPortMap() {
137         return mRequiredProtoPortMap;
138     }
139 
140     /**
141      * This specifies the maximum acceptable BSS load policy.  This is used to prevent device
142      * from joining an AP whose channel is overly congested with traffic.
143      * Using Integer.MIN_VALUE to indicate unset value.
144      */
145     private int mMaximumBssLoadValue = Integer.MIN_VALUE;
setMaximumBssLoadValue(int maximumBssLoadValue)146     public void setMaximumBssLoadValue(int maximumBssLoadValue) {
147         mMaximumBssLoadValue = maximumBssLoadValue;
148     }
getMaximumBssLoadValue()149     public int getMaximumBssLoadValue() {
150         return mMaximumBssLoadValue;
151     }
152 
153     /**
154      * Policy associated with a roaming provider.  This specifies a priority associated
155      * with a roaming provider for given list of countries.
156      *
157      * Contains field under PerProviderSubscription/Policy/PreferredRoamingPartnerList.
158      */
159     public static final class RoamingPartner implements Parcelable {
160         /**
161          * FQDN of the roaming partner.
162          */
163         private String mFqdn = null;
setFqdn(String fqdn)164         public void setFqdn(String fqdn) {
165             mFqdn = fqdn;
166         }
getFqdn()167         public String getFqdn() {
168             return mFqdn;
169         }
170 
171         /**
172          * Flag indicating the exact match of FQDN is required for FQDN matching.
173          *
174          * When this flag is set to false, sub-domain matching is used.  For example, when
175          * {@link #fqdn} s set to "example.com", "host.example.com" would be a match.
176          */
177         private boolean mFqdnExactMatch = false;
setFqdnExactMatch(boolean fqdnExactMatch)178         public void setFqdnExactMatch(boolean fqdnExactMatch) {
179             mFqdnExactMatch = fqdnExactMatch;
180         }
getFqdnExactMatch()181         public boolean getFqdnExactMatch() {
182             return mFqdnExactMatch;
183         }
184 
185         /**
186          * Priority associated with this roaming partner policy.
187          * Using Integer.MIN_VALUE to indicate unset value.
188          */
189         private int mPriority = Integer.MIN_VALUE;
setPriority(int priority)190         public void setPriority(int priority) {
191             mPriority = priority;
192         }
getPriority()193         public int getPriority() {
194             return mPriority;
195         }
196 
197         /**
198          * A string contained One or more, comma delimited (i.e., ",") ISO/IEC 3166-1 two
199          * character country strings or the country-independent value, "*".
200          */
201         private String mCountries = null;
setCountries(String countries)202         public void setCountries(String countries) {
203             mCountries = countries;
204         }
getCountries()205         public String getCountries() {
206             return mCountries;
207         }
208 
RoamingPartner()209         public RoamingPartner() {}
210 
RoamingPartner(RoamingPartner source)211         public RoamingPartner(RoamingPartner source) {
212             if (source != null) {
213                 mFqdn = source.mFqdn;
214                 mFqdnExactMatch = source.mFqdnExactMatch;
215                 mPriority = source.mPriority;
216                 mCountries = source.mCountries;
217             }
218         }
219 
220         @Override
describeContents()221         public int describeContents() {
222             return 0;
223         }
224 
225         @Override
writeToParcel(Parcel dest, int flags)226         public void writeToParcel(Parcel dest, int flags) {
227             dest.writeString(mFqdn);
228             dest.writeInt(mFqdnExactMatch ? 1 : 0);
229             dest.writeInt(mPriority);
230             dest.writeString(mCountries);
231         }
232 
233         @Override
equals(Object thatObject)234         public boolean equals(Object thatObject) {
235             if (this == thatObject) {
236                 return true;
237             }
238             if (!(thatObject instanceof RoamingPartner)) {
239                 return false;
240             }
241 
242             RoamingPartner that = (RoamingPartner) thatObject;
243             return TextUtils.equals(mFqdn, that.mFqdn)
244                     && mFqdnExactMatch == that.mFqdnExactMatch
245                     && mPriority == that.mPriority
246                     && TextUtils.equals(mCountries, that.mCountries);
247         }
248 
249         @Override
hashCode()250         public int hashCode() {
251             return Objects.hash(mFqdn, mFqdnExactMatch, mPriority, mCountries);
252         }
253 
254         @Override
toString()255         public String toString() {
256             StringBuilder builder = new StringBuilder();
257             builder.append("FQDN: ").append(mFqdn).append("\n");
258             builder.append("ExactMatch: ").append("mFqdnExactMatch").append("\n");
259             builder.append("Priority: ").append(mPriority).append("\n");
260             builder.append("Countries: ").append(mCountries).append("\n");
261             return builder.toString();
262         }
263 
264         /**
265          * Validate RoamingParnter data.
266          *
267          * @return true on success
268          * @hide
269          */
validate()270         public boolean validate() {
271             if (TextUtils.isEmpty(mFqdn)) {
272                 Log.d(TAG, "Missing FQDN");
273                 return false;
274             }
275             if (TextUtils.isEmpty(mCountries)) {
276                 Log.d(TAG, "Missing countries");
277                 return false;
278             }
279             return true;
280         }
281 
282         public static final @android.annotation.NonNull Creator<RoamingPartner> CREATOR =
283             new Creator<RoamingPartner>() {
284                 @Override
285                 public RoamingPartner createFromParcel(Parcel in) {
286                     RoamingPartner roamingPartner = new RoamingPartner();
287                     roamingPartner.setFqdn(in.readString());
288                     roamingPartner.setFqdnExactMatch(in.readInt() != 0);
289                     roamingPartner.setPriority(in.readInt());
290                     roamingPartner.setCountries(in.readString());
291                     return roamingPartner;
292                 }
293 
294                 @Override
295                 public RoamingPartner[] newArray(int size) {
296                     return new RoamingPartner[size];
297                 }
298             };
299     }
300     private List<RoamingPartner> mPreferredRoamingPartnerList = null;
setPreferredRoamingPartnerList(List<RoamingPartner> partnerList)301     public void setPreferredRoamingPartnerList(List<RoamingPartner> partnerList) {
302         mPreferredRoamingPartnerList = partnerList;
303     }
getPreferredRoamingPartnerList()304     public List<RoamingPartner> getPreferredRoamingPartnerList() {
305         return mPreferredRoamingPartnerList;
306     }
307 
308     /**
309      * Meta data used for policy update.
310      */
311     private UpdateParameter mPolicyUpdate = null;
setPolicyUpdate(UpdateParameter policyUpdate)312     public void setPolicyUpdate(UpdateParameter policyUpdate) {
313         mPolicyUpdate = policyUpdate;
314     }
getPolicyUpdate()315     public UpdateParameter getPolicyUpdate() {
316         return mPolicyUpdate;
317     }
318 
319     /**
320      * Constructor for creating Policy with default values.
321      */
Policy()322     public Policy() {}
323 
324     /**
325      * Copy constructor.
326      *
327      * @param source The source to copy from
328      */
Policy(Policy source)329     public Policy(Policy source) {
330         if (source == null) {
331             return;
332         }
333         mMinHomeDownlinkBandwidth = source.mMinHomeDownlinkBandwidth;
334         mMinHomeUplinkBandwidth = source.mMinHomeUplinkBandwidth;
335         mMinRoamingDownlinkBandwidth = source.mMinRoamingDownlinkBandwidth;
336         mMinRoamingUplinkBandwidth = source.mMinRoamingUplinkBandwidth;
337         mMaximumBssLoadValue = source.mMaximumBssLoadValue;
338         if (source.mExcludedSsidList != null) {
339             mExcludedSsidList = Arrays.copyOf(source.mExcludedSsidList,
340                     source.mExcludedSsidList.length);
341         }
342         if (source.mRequiredProtoPortMap != null) {
343             mRequiredProtoPortMap = Collections.unmodifiableMap(source.mRequiredProtoPortMap);
344         }
345         if (source.mPreferredRoamingPartnerList != null) {
346             mPreferredRoamingPartnerList = Collections.unmodifiableList(
347                     source.mPreferredRoamingPartnerList);
348         }
349         if (source.mPolicyUpdate != null) {
350             mPolicyUpdate = new UpdateParameter(source.mPolicyUpdate);
351         }
352     }
353 
354     @Override
describeContents()355     public int describeContents() {
356         return 0;
357     }
358 
359     @Override
writeToParcel(Parcel dest, int flags)360     public void writeToParcel(Parcel dest, int flags) {
361         dest.writeLong(mMinHomeDownlinkBandwidth);
362         dest.writeLong(mMinHomeUplinkBandwidth);
363         dest.writeLong(mMinRoamingDownlinkBandwidth);
364         dest.writeLong(mMinRoamingUplinkBandwidth);
365         dest.writeStringArray(mExcludedSsidList);
366         writeProtoPortMap(dest, mRequiredProtoPortMap);
367         dest.writeInt(mMaximumBssLoadValue);
368         writeRoamingPartnerList(dest, flags, mPreferredRoamingPartnerList);
369         dest.writeParcelable(mPolicyUpdate, flags);
370     }
371 
372     @Override
equals(Object thatObject)373     public boolean equals(Object thatObject) {
374         if (this == thatObject) {
375             return true;
376         }
377         if (!(thatObject instanceof Policy)) {
378             return false;
379         }
380         Policy that = (Policy) thatObject;
381 
382         return mMinHomeDownlinkBandwidth == that.mMinHomeDownlinkBandwidth
383                 && mMinHomeUplinkBandwidth == that.mMinHomeUplinkBandwidth
384                 && mMinRoamingDownlinkBandwidth == that.mMinRoamingDownlinkBandwidth
385                 && mMinRoamingUplinkBandwidth == that.mMinRoamingUplinkBandwidth
386                 && Arrays.equals(mExcludedSsidList, that.mExcludedSsidList)
387                 && (mRequiredProtoPortMap == null ? that.mRequiredProtoPortMap == null
388                         : mRequiredProtoPortMap.equals(that.mRequiredProtoPortMap))
389                 && mMaximumBssLoadValue == that.mMaximumBssLoadValue
390                 && (mPreferredRoamingPartnerList == null
391                         ? that.mPreferredRoamingPartnerList == null
392                         : mPreferredRoamingPartnerList.equals(that.mPreferredRoamingPartnerList))
393                 && (mPolicyUpdate == null ? that.mPolicyUpdate == null
394                         : mPolicyUpdate.equals(that.mPolicyUpdate));
395     }
396 
397     @Override
hashCode()398     public int hashCode() {
399         return Objects.hash(mMinHomeDownlinkBandwidth, mMinHomeUplinkBandwidth,
400                 mMinRoamingDownlinkBandwidth, mMinRoamingUplinkBandwidth, mExcludedSsidList,
401                 mRequiredProtoPortMap, mMaximumBssLoadValue, mPreferredRoamingPartnerList,
402                 mPolicyUpdate);
403     }
404 
405     @Override
toString()406     public String toString() {
407         StringBuilder builder = new StringBuilder();
408         builder.append("MinHomeDownlinkBandwidth: ").append(mMinHomeDownlinkBandwidth)
409                 .append("\n");
410         builder.append("MinHomeUplinkBandwidth: ").append(mMinHomeUplinkBandwidth).append("\n");
411         builder.append("MinRoamingDownlinkBandwidth: ").append(mMinRoamingDownlinkBandwidth)
412                 .append("\n");
413         builder.append("MinRoamingUplinkBandwidth: ").append(mMinRoamingUplinkBandwidth)
414                 .append("\n");
415         builder.append("ExcludedSSIDList: ").append(mExcludedSsidList).append("\n");
416         builder.append("RequiredProtoPortMap: ").append(mRequiredProtoPortMap).append("\n");
417         builder.append("MaximumBSSLoadValue: ").append(mMaximumBssLoadValue).append("\n");
418         builder.append("PreferredRoamingPartnerList: ").append(mPreferredRoamingPartnerList)
419                 .append("\n");
420         if (mPolicyUpdate != null) {
421             builder.append("PolicyUpdate Begin ---\n");
422             builder.append(mPolicyUpdate);
423             builder.append("PolicyUpdate End ---\n");
424         }
425         return builder.toString();
426     }
427 
428     /**
429      * Validate Policy data.
430      *
431      * @return true on success
432      * @hide
433      */
validate()434     public boolean validate() {
435         if (mPolicyUpdate == null) {
436             Log.d(TAG, "PolicyUpdate not specified");
437             return false;
438         }
439         if (!mPolicyUpdate.validate()) {
440             return false;
441         }
442 
443         // Validate SSID exclusion list.
444         if (mExcludedSsidList != null) {
445             if (mExcludedSsidList.length > MAX_EXCLUSION_SSIDS) {
446                 Log.d(TAG, "SSID exclusion list size exceeded the max: "
447                         + mExcludedSsidList.length);
448                 return false;
449             }
450             for (String ssid : mExcludedSsidList) {
451                 if (ssid.getBytes(StandardCharsets.UTF_8).length > MAX_SSID_BYTES) {
452                     Log.d(TAG, "Invalid SSID: " + ssid);
453                     return false;
454                 }
455             }
456         }
457         // Validate required protocol to port map.
458         if (mRequiredProtoPortMap != null) {
459             for (Map.Entry<Integer, String> entry : mRequiredProtoPortMap.entrySet()) {
460                 String portNumber = entry.getValue();
461                 if (portNumber.getBytes(StandardCharsets.UTF_8).length > MAX_PORT_STRING_BYTES) {
462                     Log.d(TAG, "PortNumber string bytes exceeded the max: " + portNumber);
463                     return false;
464                 }
465             }
466         }
467         // Validate preferred roaming partner list.
468         if (mPreferredRoamingPartnerList != null) {
469             for (RoamingPartner partner : mPreferredRoamingPartnerList) {
470                 if (!partner.validate()) {
471                     return false;
472                 }
473             }
474         }
475         return true;
476     }
477 
478     public static final @android.annotation.NonNull Creator<Policy> CREATOR =
479         new Creator<Policy>() {
480             @Override
481             public Policy createFromParcel(Parcel in) {
482                 Policy policy = new Policy();
483                 policy.setMinHomeDownlinkBandwidth(in.readLong());
484                 policy.setMinHomeUplinkBandwidth(in.readLong());
485                 policy.setMinRoamingDownlinkBandwidth(in.readLong());
486                 policy.setMinRoamingUplinkBandwidth(in.readLong());
487                 policy.setExcludedSsidList(in.createStringArray());
488                 policy.setRequiredProtoPortMap(readProtoPortMap(in));
489                 policy.setMaximumBssLoadValue(in.readInt());
490                 policy.setPreferredRoamingPartnerList(readRoamingPartnerList(in));
491                 policy.setPolicyUpdate(in.readParcelable(null));
492                 return policy;
493             }
494 
495             @Override
496             public Policy[] newArray(int size) {
497                 return new Policy[size];
498             }
499 
500             /**
501              * Helper function for reading IP Protocol to Port Number map from a Parcel.
502              *
503              * @param in The Parcel to read from
504              * @return Map of IP protocol to port number
505              */
506             private Map<Integer, String> readProtoPortMap(Parcel in) {
507                 int size = in.readInt();
508                 if (size == NULL_VALUE) {
509                     return null;
510                 }
511                 Map<Integer, String> protoPortMap = new HashMap<>(size);
512                 for (int i = 0; i < size; i++) {
513                     int key = in.readInt();
514                     String value = in.readString();
515                     protoPortMap.put(key, value);
516                 }
517                 return protoPortMap;
518             }
519 
520             /**
521              * Helper function for reading roaming partner list from a Parcel.
522              *
523              * @param in The Parcel to read from
524              * @return List of roaming partners
525              */
526             private List<RoamingPartner> readRoamingPartnerList(Parcel in) {
527                 int size = in.readInt();
528                 if (size == NULL_VALUE) {
529                     return null;
530                 }
531                 List<RoamingPartner> partnerList = new ArrayList<>();
532                 for (int i = 0; i < size; i++) {
533                     partnerList.add(in.readParcelable(null));
534                 }
535                 return partnerList;
536             }
537 
538         };
539 
540     /**
541      * Helper function for writing IP Protocol to Port Number map to a Parcel.
542      *
543      * @param dest The Parcel to write to
544      * @param protoPortMap The map to write
545      */
writeProtoPortMap(Parcel dest, Map<Integer, String> protoPortMap)546     private static void writeProtoPortMap(Parcel dest, Map<Integer, String> protoPortMap) {
547         if (protoPortMap == null) {
548             dest.writeInt(NULL_VALUE);
549             return;
550         }
551         dest.writeInt(protoPortMap.size());
552         for (Map.Entry<Integer, String> entry : protoPortMap.entrySet()) {
553             dest.writeInt(entry.getKey());
554             dest.writeString(entry.getValue());
555         }
556     }
557 
558     /**
559      * Helper function for writing roaming partner list to a Parcel.
560      *
561      * @param dest The Parcel to write to
562      * @param flags The flag about how the object should be written
563      * @param partnerList The partner list to write
564      */
writeRoamingPartnerList(Parcel dest, int flags, List<RoamingPartner> partnerList)565     private static void writeRoamingPartnerList(Parcel dest, int flags,
566             List<RoamingPartner> partnerList) {
567         if (partnerList == null) {
568             dest.writeInt(NULL_VALUE);
569             return;
570         }
571         dest.writeInt(partnerList.size());
572         for (RoamingPartner partner : partnerList) {
573             dest.writeParcelable(partner, flags);
574         }
575     }
576 }
577