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
17package android.net.wifi.hotspot2.pps;
18
19import android.os.Parcel;
20import android.os.Parcelable;
21import android.text.TextUtils;
22import android.util.Log;
23
24import java.nio.charset.StandardCharsets;
25import java.util.ArrayList;
26import java.util.Arrays;
27import java.util.Collections;
28import java.util.HashMap;
29import java.util.List;
30import java.util.Map;
31import 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 */
46public 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;
79    public void setMinHomeDownlinkBandwidth(long minHomeDownlinkBandwidth) {
80        mMinHomeDownlinkBandwidth = minHomeDownlinkBandwidth;
81    }
82    public long getMinHomeDownlinkBandwidth() {
83        return mMinHomeDownlinkBandwidth;
84    }
85    private long mMinHomeUplinkBandwidth = Long.MIN_VALUE;
86    public void setMinHomeUplinkBandwidth(long minHomeUplinkBandwidth) {
87        mMinHomeUplinkBandwidth = minHomeUplinkBandwidth;
88    }
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;
103    public void setMinRoamingDownlinkBandwidth(long minRoamingDownlinkBandwidth) {
104        mMinRoamingDownlinkBandwidth = minRoamingDownlinkBandwidth;
105    }
106    public long getMinRoamingDownlinkBandwidth() {
107        return mMinRoamingDownlinkBandwidth;
108    }
109    private long mMinRoamingUplinkBandwidth = Long.MIN_VALUE;
110    public void setMinRoamingUplinkBandwidth(long minRoamingUplinkBandwidth) {
111        mMinRoamingUplinkBandwidth = minRoamingUplinkBandwidth;
112    }
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;
121    public void setExcludedSsidList(String[] excludedSsidList) {
122        mExcludedSsidList = excludedSsidList;
123    }
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;
133    public void setRequiredProtoPortMap(Map<Integer, String> requiredProtoPortMap) {
134        mRequiredProtoPortMap = requiredProtoPortMap;
135    }
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;
146    public void setMaximumBssLoadValue(int maximumBssLoadValue) {
147        mMaximumBssLoadValue = maximumBssLoadValue;
148    }
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;
164        public void setFqdn(String fqdn) {
165            mFqdn = fqdn;
166        }
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;
178        public void setFqdnExactMatch(boolean fqdnExactMatch) {
179            mFqdnExactMatch = fqdnExactMatch;
180        }
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;
190        public void setPriority(int priority) {
191            mPriority = priority;
192        }
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;
202        public void setCountries(String countries) {
203            mCountries = countries;
204        }
205        public String getCountries() {
206            return mCountries;
207        }
208
209        public RoamingPartner() {}
210
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
221        public int describeContents() {
222            return 0;
223        }
224
225        @Override
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
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
250        public int hashCode() {
251            return Objects.hash(mFqdn, mFqdnExactMatch, mPriority, mCountries);
252        }
253
254        @Override
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         */
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 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;
301    public void setPreferredRoamingPartnerList(List<RoamingPartner> partnerList) {
302        mPreferredRoamingPartnerList = partnerList;
303    }
304    public List<RoamingPartner> getPreferredRoamingPartnerList() {
305        return mPreferredRoamingPartnerList;
306    }
307
308    /**
309     * Meta data used for policy update.
310     */
311    private UpdateParameter mPolicyUpdate = null;
312    public void setPolicyUpdate(UpdateParameter policyUpdate) {
313        mPolicyUpdate = policyUpdate;
314    }
315    public UpdateParameter getPolicyUpdate() {
316        return mPolicyUpdate;
317    }
318
319    /**
320     * Constructor for creating Policy with default values.
321     */
322    public Policy() {}
323
324    /**
325     * Copy constructor.
326     *
327     * @param source The source to copy from
328     */
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
355    public int describeContents() {
356        return 0;
357    }
358
359    @Override
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
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
398    public int hashCode() {
399        return Objects.hash(mMinHomeDownlinkBandwidth, mMinHomeUplinkBandwidth,
400                mMinRoamingDownlinkBandwidth, mMinRoamingUplinkBandwidth, mExcludedSsidList,
401                mRequiredProtoPortMap, mMaximumBssLoadValue, mPreferredRoamingPartnerList,
402                mPolicyUpdate);
403    }
404
405    @Override
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     */
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 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     */
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     */
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