1/**
2 * Copyright (c) 2016, 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;
18
19import android.net.wifi.hotspot2.pps.Credential;
20import android.net.wifi.hotspot2.pps.HomeSp;
21import android.net.wifi.hotspot2.pps.Policy;
22import android.net.wifi.hotspot2.pps.UpdateParameter;
23import android.os.Parcelable;
24import android.text.TextUtils;
25import android.util.Log;
26import android.os.Parcel;
27
28import java.nio.charset.StandardCharsets;
29import java.util.Arrays;
30import java.util.Collections;
31import java.util.Date;
32import java.util.HashMap;
33import java.util.Map;
34import java.util.Objects;
35
36/**
37 * Class representing Passpoint configuration.  This contains configurations specified in
38 * PerProviderSubscription (PPS) Management Object (MO) tree.
39 *
40 * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
41 * Release 2 Technical Specification.
42 */
43public final class PasspointConfiguration implements Parcelable {
44    private static final String TAG = "PasspointConfiguration";
45
46    /**
47     * Number of bytes for certificate SHA-256 fingerprint byte array.
48     */
49    private static final int CERTIFICATE_SHA256_BYTES = 32;
50
51    /**
52     * Maximum bytes for URL string.
53     */
54    private static final int MAX_URL_BYTES = 1023;
55
56    /**
57     * Integer value used for indicating null value in the Parcel.
58     */
59    private static final int NULL_VALUE = -1;
60
61    /**
62     * Configurations under HomeSp subtree.
63     */
64    private HomeSp mHomeSp = null;
65    /**
66     * Set the Home SP (Service Provider) information.
67     *
68     * @param homeSp The Home SP information to set to
69     */
70    public void setHomeSp(HomeSp homeSp) { mHomeSp = homeSp; }
71    /**
72     * Get the Home SP (Service Provider) information.
73     *
74     * @return Home SP information
75     */
76    public HomeSp getHomeSp() { return mHomeSp; }
77
78    /**
79     * Configurations under Credential subtree.
80     */
81    private Credential mCredential = null;
82    /**
83     * Set the credential information.
84     *
85     * @param credential The credential information to set to
86     */
87    public void setCredential(Credential credential) {
88        mCredential = credential;
89    }
90    /**
91     * Get the credential information.
92     *
93     * @return credential information
94     */
95    public Credential getCredential() {
96        return mCredential;
97    }
98
99    /**
100     * Configurations under Policy subtree.
101     */
102    private Policy mPolicy = null;
103    /**
104     * @hide
105     */
106    public void setPolicy(Policy policy) {
107        mPolicy = policy;
108    }
109    /**
110     * @hide
111     */
112    public Policy getPolicy() {
113        return mPolicy;
114    }
115
116    /**
117     * Meta data for performing subscription update.
118     */
119    private UpdateParameter mSubscriptionUpdate = null;
120    /**
121     * @hide
122     */
123    public void setSubscriptionUpdate(UpdateParameter subscriptionUpdate) {
124        mSubscriptionUpdate = subscriptionUpdate;
125    }
126    /**
127     * @hide
128     */
129    public UpdateParameter getSubscriptionUpdate() {
130        return mSubscriptionUpdate;
131    }
132
133    /**
134     * List of HTTPS URL for retrieving trust root certificate and the corresponding SHA-256
135     * fingerprint of the certificate.  The certificates are used for verifying AAA server's
136     * identity during EAP authentication.
137     */
138    private Map<String, byte[]> mTrustRootCertList = null;
139    /**
140     * @hide
141     */
142    public void setTrustRootCertList(Map<String, byte[]> trustRootCertList) {
143        mTrustRootCertList = trustRootCertList;
144    }
145    /**
146     * @hide
147     */
148    public Map<String, byte[]> getTrustRootCertList() {
149        return mTrustRootCertList;
150    }
151
152    /**
153     * Set by the subscription server, updated every time the configuration is updated by
154     * the subscription server.
155     *
156     * Use Integer.MIN_VALUE to indicate unset value.
157     */
158    private int mUpdateIdentifier = Integer.MIN_VALUE;
159    /**
160     * @hide
161     */
162    public void setUpdateIdentifier(int updateIdentifier) {
163        mUpdateIdentifier = updateIdentifier;
164    }
165    /**
166     * @hide
167     */
168    public int getUpdateIdentifier() {
169        return mUpdateIdentifier;
170    }
171
172    /**
173     * The priority of the credential.
174     *
175     * Use Integer.MIN_VALUE to indicate unset value.
176     */
177    private int mCredentialPriority = Integer.MIN_VALUE;
178    /**
179     * @hide
180     */
181    public void setCredentialPriority(int credentialPriority) {
182        mCredentialPriority = credentialPriority;
183    }
184    /**
185     * @hide
186     */
187    public int getCredentialPriority() {
188        return mCredentialPriority;
189    }
190
191    /**
192     * The time this subscription is created. It is in the format of number
193     * of milliseconds since January 1, 1970, 00:00:00 GMT.
194     *
195     * Use Long.MIN_VALUE to indicate unset value.
196     */
197    private long mSubscriptionCreationTimeInMillis = Long.MIN_VALUE;
198    /**
199     * @hide
200     */
201    public void setSubscriptionCreationTimeInMillis(long subscriptionCreationTimeInMillis) {
202        mSubscriptionCreationTimeInMillis = subscriptionCreationTimeInMillis;
203    }
204    /**
205     * @hide
206     */
207    public long getSubscriptionCreationTimeInMillis() {
208        return mSubscriptionCreationTimeInMillis;
209    }
210
211    /**
212     * The time this subscription will expire. It is in the format of number
213     * of milliseconds since January 1, 1970, 00:00:00 GMT.
214     *
215     * Use Long.MIN_VALUE to indicate unset value.
216     */
217    private long mSubscriptionExpirationTimeInMillis = Long.MIN_VALUE;
218    /**
219     * @hide
220     */
221    public void setSubscriptionExpirationTimeInMillis(long subscriptionExpirationTimeInMillis) {
222        mSubscriptionExpirationTimeInMillis = subscriptionExpirationTimeInMillis;
223    }
224    /**
225     * @hide
226     */
227    public long getSubscriptionExpirationTimeInMillis() {
228        return mSubscriptionExpirationTimeInMillis;
229    }
230
231    /**
232     * The type of the subscription.  This is defined by the provider and the value is provider
233     * specific.
234     */
235    private String mSubscriptionType = null;
236    /**
237     * @hide
238     */
239    public void setSubscriptionType(String subscriptionType) {
240        mSubscriptionType = subscriptionType;
241    }
242    /**
243     * @hide
244     */
245    public String getSubscriptionType() {
246        return mSubscriptionType;
247    }
248
249    /**
250     * The time period for usage statistics accumulation. A value of zero means that usage
251     * statistics are not accumulated on a periodic basis (e.g., a one-time limit for
252     * “pay as you go” - PAYG service). A non-zero value specifies the usage interval in minutes.
253     */
254    private long mUsageLimitUsageTimePeriodInMinutes = Long.MIN_VALUE;
255    /**
256     * @hide
257     */
258    public void setUsageLimitUsageTimePeriodInMinutes(long usageLimitUsageTimePeriodInMinutes) {
259        mUsageLimitUsageTimePeriodInMinutes = usageLimitUsageTimePeriodInMinutes;
260    }
261    /**
262     * @hide
263     */
264    public long getUsageLimitUsageTimePeriodInMinutes() {
265        return mUsageLimitUsageTimePeriodInMinutes;
266    }
267
268    /**
269     * The time at which usage statistic accumulation  begins.  It is in the format of number
270     * of milliseconds since January 1, 1970, 00:00:00 GMT.
271     *
272     * Use Long.MIN_VALUE to indicate unset value.
273     */
274    private long mUsageLimitStartTimeInMillis = Long.MIN_VALUE;
275    /**
276     * @hide
277     */
278    public void setUsageLimitStartTimeInMillis(long usageLimitStartTimeInMillis) {
279        mUsageLimitStartTimeInMillis = usageLimitStartTimeInMillis;
280    }
281    /**
282     * @hide
283     */
284    public long getUsageLimitStartTimeInMillis() {
285        return mUsageLimitStartTimeInMillis;
286    }
287
288    /**
289     * The cumulative data limit in megabytes for the {@link #usageLimitUsageTimePeriodInMinutes}.
290     * A value of zero indicate unlimited data usage.
291     *
292     * Use Long.MIN_VALUE to indicate unset value.
293     */
294    private long mUsageLimitDataLimit = Long.MIN_VALUE;
295    /**
296     * @hide
297     */
298    public void setUsageLimitDataLimit(long usageLimitDataLimit) {
299        mUsageLimitDataLimit = usageLimitDataLimit;
300    }
301    /**
302     * @hide
303     */
304    public long getUsageLimitDataLimit() {
305        return mUsageLimitDataLimit;
306    }
307
308    /**
309     * The cumulative time limit in minutes for the {@link #usageLimitUsageTimePeriodInMinutes}.
310     * A value of zero indicate unlimited time usage.
311     */
312    private long mUsageLimitTimeLimitInMinutes = Long.MIN_VALUE;
313    /**
314     * @hide
315     */
316    public void setUsageLimitTimeLimitInMinutes(long usageLimitTimeLimitInMinutes) {
317        mUsageLimitTimeLimitInMinutes = usageLimitTimeLimitInMinutes;
318    }
319    /**
320     * @hide
321     */
322    public long getUsageLimitTimeLimitInMinutes() {
323        return mUsageLimitTimeLimitInMinutes;
324    }
325
326    /**
327     * Constructor for creating PasspointConfiguration with default values.
328     */
329    public PasspointConfiguration() {}
330
331    /**
332     * Copy constructor.
333     *
334     * @param source The source to copy from
335     */
336    public PasspointConfiguration(PasspointConfiguration source) {
337        if (source == null) {
338            return;
339        }
340
341        if (source.mHomeSp != null) {
342            mHomeSp = new HomeSp(source.mHomeSp);
343        }
344        if (source.mCredential != null) {
345            mCredential = new Credential(source.mCredential);
346        }
347        if (source.mPolicy != null) {
348            mPolicy = new Policy(source.mPolicy);
349        }
350        if (source.mTrustRootCertList != null) {
351            mTrustRootCertList = Collections.unmodifiableMap(source.mTrustRootCertList);
352        }
353        if (source.mSubscriptionUpdate != null) {
354            mSubscriptionUpdate = new UpdateParameter(source.mSubscriptionUpdate);
355        }
356        mUpdateIdentifier = source.mUpdateIdentifier;
357        mCredentialPriority = source.mCredentialPriority;
358        mSubscriptionCreationTimeInMillis = source.mSubscriptionCreationTimeInMillis;
359        mSubscriptionExpirationTimeInMillis = source.mSubscriptionExpirationTimeInMillis;
360        mSubscriptionType = source.mSubscriptionType;
361        mUsageLimitDataLimit = source.mUsageLimitDataLimit;
362        mUsageLimitStartTimeInMillis = source.mUsageLimitStartTimeInMillis;
363        mUsageLimitTimeLimitInMinutes = source.mUsageLimitTimeLimitInMinutes;
364        mUsageLimitUsageTimePeriodInMinutes = source.mUsageLimitUsageTimePeriodInMinutes;
365    }
366
367    @Override
368    public int describeContents() {
369        return 0;
370    }
371
372    @Override
373    public void writeToParcel(Parcel dest, int flags) {
374        dest.writeParcelable(mHomeSp, flags);
375        dest.writeParcelable(mCredential, flags);
376        dest.writeParcelable(mPolicy, flags);
377        dest.writeParcelable(mSubscriptionUpdate, flags);
378        writeTrustRootCerts(dest, mTrustRootCertList);
379        dest.writeInt(mUpdateIdentifier);
380        dest.writeInt(mCredentialPriority);
381        dest.writeLong(mSubscriptionCreationTimeInMillis);
382        dest.writeLong(mSubscriptionExpirationTimeInMillis);
383        dest.writeString(mSubscriptionType);
384        dest.writeLong(mUsageLimitUsageTimePeriodInMinutes);
385        dest.writeLong(mUsageLimitStartTimeInMillis);
386        dest.writeLong(mUsageLimitDataLimit);
387        dest.writeLong(mUsageLimitTimeLimitInMinutes);
388    }
389
390    @Override
391    public boolean equals(Object thatObject) {
392        if (this == thatObject) {
393            return true;
394        }
395        if (!(thatObject instanceof PasspointConfiguration)) {
396            return false;
397        }
398        PasspointConfiguration that = (PasspointConfiguration) thatObject;
399        return (mHomeSp == null ? that.mHomeSp == null : mHomeSp.equals(that.mHomeSp))
400                && (mCredential == null ? that.mCredential == null
401                        : mCredential.equals(that.mCredential))
402                && (mPolicy == null ? that.mPolicy == null : mPolicy.equals(that.mPolicy))
403                && (mSubscriptionUpdate == null ? that.mSubscriptionUpdate == null
404                        : mSubscriptionUpdate.equals(that.mSubscriptionUpdate))
405                && isTrustRootCertListEquals(mTrustRootCertList, that.mTrustRootCertList)
406                && mUpdateIdentifier == that.mUpdateIdentifier
407                && mCredentialPriority == that.mCredentialPriority
408                && mSubscriptionCreationTimeInMillis == that.mSubscriptionCreationTimeInMillis
409                && mSubscriptionExpirationTimeInMillis == that.mSubscriptionExpirationTimeInMillis
410                && TextUtils.equals(mSubscriptionType, that.mSubscriptionType)
411                && mUsageLimitUsageTimePeriodInMinutes == that.mUsageLimitUsageTimePeriodInMinutes
412                && mUsageLimitStartTimeInMillis == that.mUsageLimitStartTimeInMillis
413                && mUsageLimitDataLimit == that.mUsageLimitDataLimit
414                && mUsageLimitTimeLimitInMinutes == that.mUsageLimitTimeLimitInMinutes;
415    }
416
417    @Override
418    public int hashCode() {
419        return Objects.hash(mHomeSp, mCredential, mPolicy, mSubscriptionUpdate, mTrustRootCertList,
420                mUpdateIdentifier, mCredentialPriority, mSubscriptionCreationTimeInMillis,
421                mSubscriptionExpirationTimeInMillis, mUsageLimitUsageTimePeriodInMinutes,
422                mUsageLimitStartTimeInMillis, mUsageLimitDataLimit, mUsageLimitTimeLimitInMinutes);
423    }
424
425    @Override
426    public String toString() {
427        StringBuilder builder = new StringBuilder();
428        builder.append("UpdateIdentifier: ").append(mUpdateIdentifier).append("\n");
429        builder.append("CredentialPriority: ").append(mCredentialPriority).append("\n");
430        builder.append("SubscriptionCreationTime: ").append(
431                mSubscriptionCreationTimeInMillis != Long.MIN_VALUE
432                ? new Date(mSubscriptionCreationTimeInMillis) : "Not specified").append("\n");
433        builder.append("SubscriptionExpirationTime: ").append(
434                mSubscriptionExpirationTimeInMillis != Long.MIN_VALUE
435                ? new Date(mSubscriptionExpirationTimeInMillis) : "Not specified").append("\n");
436        builder.append("UsageLimitStartTime: ").append(mUsageLimitStartTimeInMillis != Long.MIN_VALUE
437                ? new Date(mUsageLimitStartTimeInMillis) : "Not specified").append("\n");
438        builder.append("UsageTimePeriod: ").append(mUsageLimitUsageTimePeriodInMinutes)
439                .append("\n");
440        builder.append("UsageLimitDataLimit: ").append(mUsageLimitDataLimit).append("\n");
441        builder.append("UsageLimitTimeLimit: ").append(mUsageLimitTimeLimitInMinutes).append("\n");
442        if (mHomeSp != null) {
443            builder.append("HomeSP Begin ---\n");
444            builder.append(mHomeSp);
445            builder.append("HomeSP End ---\n");
446        }
447        if (mCredential != null) {
448            builder.append("Credential Begin ---\n");
449            builder.append(mCredential);
450            builder.append("Credential End ---\n");
451        }
452        if (mPolicy != null) {
453            builder.append("Policy Begin ---\n");
454            builder.append(mPolicy);
455            builder.append("Policy End ---\n");
456        }
457        if (mSubscriptionUpdate != null) {
458            builder.append("SubscriptionUpdate Begin ---\n");
459            builder.append(mSubscriptionUpdate);
460            builder.append("SubscriptionUpdate End ---\n");
461        }
462        if (mTrustRootCertList != null) {
463            builder.append("TrustRootCertServers: ").append(mTrustRootCertList.keySet())
464                    .append("\n");
465        }
466        return builder.toString();
467    }
468
469    /**
470     * Validate the configuration data.
471     *
472     * @return true on success or false on failure
473     * @hide
474     */
475    public boolean validate() {
476        if (mHomeSp == null || !mHomeSp.validate()) {
477            return false;
478        }
479        if (mCredential == null || !mCredential.validate()) {
480            return false;
481        }
482        if (mPolicy != null && !mPolicy.validate()) {
483            return false;
484        }
485        if (mSubscriptionUpdate != null && !mSubscriptionUpdate.validate()) {
486            return false;
487        }
488        if (mTrustRootCertList != null) {
489            for (Map.Entry<String, byte[]> entry : mTrustRootCertList.entrySet()) {
490                String url = entry.getKey();
491                byte[] certFingerprint = entry.getValue();
492                if (TextUtils.isEmpty(url)) {
493                    Log.d(TAG, "Empty URL");
494                    return false;
495                }
496                if (url.getBytes(StandardCharsets.UTF_8).length > MAX_URL_BYTES) {
497                    Log.d(TAG, "URL bytes exceeded the max: "
498                            + url.getBytes(StandardCharsets.UTF_8).length);
499                    return false;
500                }
501
502                if (certFingerprint == null) {
503                    Log.d(TAG, "Fingerprint not specified");
504                    return false;
505                }
506                if (certFingerprint.length != CERTIFICATE_SHA256_BYTES) {
507                    Log.d(TAG, "Incorrect size of trust root certificate SHA-256 fingerprint: "
508                            + certFingerprint.length);
509                    return false;
510                }
511            }
512        }
513        return true;
514    }
515
516    public static final Creator<PasspointConfiguration> CREATOR =
517        new Creator<PasspointConfiguration>() {
518            @Override
519            public PasspointConfiguration createFromParcel(Parcel in) {
520                PasspointConfiguration config = new PasspointConfiguration();
521                config.setHomeSp(in.readParcelable(null));
522                config.setCredential(in.readParcelable(null));
523                config.setPolicy(in.readParcelable(null));
524                config.setSubscriptionUpdate(in.readParcelable(null));
525                config.setTrustRootCertList(readTrustRootCerts(in));
526                config.setUpdateIdentifier(in.readInt());
527                config.setCredentialPriority(in.readInt());
528                config.setSubscriptionCreationTimeInMillis(in.readLong());
529                config.setSubscriptionExpirationTimeInMillis(in.readLong());
530                config.setSubscriptionType(in.readString());
531                config.setUsageLimitUsageTimePeriodInMinutes(in.readLong());
532                config.setUsageLimitStartTimeInMillis(in.readLong());
533                config.setUsageLimitDataLimit(in.readLong());
534                config.setUsageLimitTimeLimitInMinutes(in.readLong());
535                return config;
536            }
537
538            @Override
539            public PasspointConfiguration[] newArray(int size) {
540                return new PasspointConfiguration[size];
541            }
542
543            /**
544             * Helper function for reading trust root certificate info list from a Parcel.
545             *
546             * @param in The Parcel to read from
547             * @return The list of trust root certificate URL with the corresponding certificate
548             *         fingerprint
549             */
550            private Map<String, byte[]> readTrustRootCerts(Parcel in) {
551                int size = in.readInt();
552                if (size == NULL_VALUE) {
553                    return null;
554                }
555                Map<String, byte[]> trustRootCerts = new HashMap<>(size);
556                for (int i = 0; i < size; i++) {
557                    String key = in.readString();
558                    byte[] value = in.createByteArray();
559                    trustRootCerts.put(key, value);
560                }
561                return trustRootCerts;
562            }
563        };
564
565    /**
566     * Helper function for writing trust root certificate information list.
567     *
568     * @param dest The Parcel to write to
569     * @param trustRootCerts The list of trust root certificate URL with the corresponding
570     *                       certificate fingerprint
571     */
572    private static void writeTrustRootCerts(Parcel dest, Map<String, byte[]> trustRootCerts) {
573        if (trustRootCerts == null) {
574            dest.writeInt(NULL_VALUE);
575            return;
576        }
577        dest.writeInt(trustRootCerts.size());
578        for (Map.Entry<String, byte[]> entry : trustRootCerts.entrySet()) {
579            dest.writeString(entry.getKey());
580            dest.writeByteArray(entry.getValue());
581        }
582    }
583
584    /**
585     * Helper function for comparing two trust root certificate list.  Cannot use Map#equals
586     * method since the value type (byte[]) doesn't override equals method.
587     *
588     * @param list1 The first trust root certificate list
589     * @param list2 The second trust root certificate list
590     * @return true if the two list are equal
591     */
592    private static boolean isTrustRootCertListEquals(Map<String, byte[]> list1,
593            Map<String, byte[]> list2) {
594        if (list1 == null || list2 == null) {
595            return list1 == list2;
596        }
597        if (list1.size() != list2.size()) {
598            return false;
599        }
600        for (Map.Entry<String, byte[]> entry : list1.entrySet()) {
601            if (!Arrays.equals(entry.getValue(), list2.get(entry.getKey()))) {
602                return false;
603            }
604        }
605        return true;
606    }
607}
608