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