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 
17 package android.net.wifi.hotspot2.pps;
18 
19 import static android.net.wifi.hotspot2.PasspointConfiguration.MAX_HESSID_VALUE;
20 import static android.net.wifi.hotspot2.PasspointConfiguration.MAX_NUMBER_OF_ENTRIES;
21 import static android.net.wifi.hotspot2.PasspointConfiguration.MAX_NUMBER_OF_OI;
22 import static android.net.wifi.hotspot2.PasspointConfiguration.MAX_OI_VALUE;
23 import static android.net.wifi.hotspot2.PasspointConfiguration.MAX_STRING_LENGTH;
24 import static android.net.wifi.hotspot2.PasspointConfiguration.MAX_URL_BYTES;
25 
26 import android.annotation.NonNull;
27 import android.annotation.Nullable;
28 import android.os.Parcel;
29 import android.os.Parcelable;
30 import android.text.TextUtils;
31 import android.util.Log;
32 
33 import java.nio.charset.StandardCharsets;
34 import java.util.Arrays;
35 import java.util.Collection;
36 import java.util.Collections;
37 import java.util.HashMap;
38 import java.util.Map;
39 import java.util.Objects;
40 
41 /**
42  * Class representing HomeSP subtree in PerProviderSubscription (PPS)
43  * Management Object (MO) tree.
44  *
45  * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
46  * Release 2 Technical Specification.
47  */
48 public final class HomeSp implements Parcelable {
49     private static final String TAG = "HomeSp";
50 
51     /**
52      * Maximum number of bytes allowed for a SSID.
53      */
54     private static final int MAX_SSID_BYTES = 32;
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      * FQDN (Fully Qualified Domain Name) of this home service provider.
63      */
64     private String mFqdn = null;
65     /**
66      * Set the FQDN (Fully Qualified Domain Name) associated with this home service provider.
67      *
68      * @param fqdn The FQDN to set to
69      */
setFqdn(String fqdn)70     public void setFqdn(String fqdn) {
71         mFqdn = fqdn;
72     }
73     /**
74      * Get the FQDN (Fully Qualified Domain Name) associated with this home service provider.
75      *
76      * @return the FQDN associated with this home service provider
77      */
getFqdn()78     public String getFqdn() {
79         return mFqdn;
80     }
81 
82     /**
83      * Friendly name of this home service provider.
84      */
85     private String mFriendlyName = null;
86     /**
87      * Set the friendly name associated with this home service provider.
88      *
89      * @param friendlyName The friendly name to set to
90      */
setFriendlyName(String friendlyName)91     public void setFriendlyName(String friendlyName) {
92         mFriendlyName = friendlyName;
93     }
94     /**
95      * Get the friendly name associated with this home service provider.
96      *
97      * @return the friendly name associated with this home service provider
98      */
getFriendlyName()99     public String getFriendlyName() {
100         return mFriendlyName;
101     }
102 
103     /**
104      * Icon URL of this home service provider.
105      */
106     private String mIconUrl = null;
107     /**
108      * @hide
109      */
setIconUrl(String iconUrl)110     public void setIconUrl(String iconUrl) {
111         mIconUrl = iconUrl;
112     }
113     /**
114      * @hide
115      */
getIconUrl()116     public String getIconUrl() {
117         return mIconUrl;
118     }
119 
120     /**
121      * <SSID, HESSID> duple of the networks that are consider home networks.
122      *
123      * According to the Section 9.1.2 of the Hotspot 2.0 Release 2 Technical Specification,
124      * all nodes in the PSS MO are encoded using UTF-8 unless stated otherwise.  Thus, the SSID
125      * string is assumed to be encoded using UTF-8.
126      */
127     private Map<String, Long> mHomeNetworkIds = null;
128     /**
129      * @hide
130      */
setHomeNetworkIds(Map<String, Long> homeNetworkIds)131     public void setHomeNetworkIds(Map<String, Long> homeNetworkIds) {
132         mHomeNetworkIds = homeNetworkIds;
133     }
134     /**
135      * @hide
136      */
getHomeNetworkIds()137     public Map<String, Long> getHomeNetworkIds() {
138         return mHomeNetworkIds;
139     }
140 
141     /**
142      * Used for determining if this provider is a member of a given Hotspot provider.
143      * Every Organization Identifiers (OIs) in this list are required to match an OI in the
144      * the Roaming Consortium advertised by a Hotspot, in order to consider this provider
145      * as a member of that Hotspot provider (e.g. successful authentication with such Hotspot
146      * is possible).
147      *
148      * Refer to HomeSP/HomeOIList subtree in PerProviderSubscription (PPS) Management Object
149      * (MO) tree for more detail.
150      */
151     private long[] mMatchAllOis = null;
152 
153     /**
154      * Set a list of HomeOIs such that all OIs in the list must match an OI in the Roaming
155      * Consortium advertised by a hotspot operator. The list set by this API will have precedence
156      * over {@link #setMatchAnyOis(long[])}, meaning the list set in {@link #setMatchAnyOis(long[])}
157      * will only be used for matching if the list set by this API is null or empty.
158      *
159      * @param matchAllOis An array of longs containing the HomeOIs
160      */
setMatchAllOis(@ullable long[] matchAllOis)161     public void setMatchAllOis(@Nullable long[] matchAllOis) {
162         mMatchAllOis = matchAllOis;
163     }
164 
165     /**
166      * Get the list of HomeOIs such that all OIs in the list must match an OI in the Roaming
167      * Consortium advertised by a hotspot operator.
168      *
169      * @return An array of longs containing the HomeOIs
170      */
getMatchAllOis()171     public @Nullable long[] getMatchAllOis() {
172         return mMatchAllOis;
173     }
174 
175     /**
176      * Used for determining if this provider is a member of a given Hotspot provider.
177      * Matching of any Organization Identifiers (OIs) in this list with an OI in the
178      * Roaming Consortium advertised by a Hotspot, will consider this provider as a member
179      * of that Hotspot provider (e.g. successful authentication with such Hotspot
180      * is possible).
181      *
182      * The list set by {@link #setMatchAllOis(long[])} will have precedence over this one, meaning
183      * this list will only be used for matching if the list set by {@link #setMatchAllOis(long[])}
184      * is null or empty.
185      *
186      * Refer to HomeSP/HomeOIList subtree in PerProviderSubscription (PPS) Management Object
187      * (MO) tree for more detail.
188      */
189     private long[] mMatchAnyOis = null;
190 
191     /**
192      * Set a list of HomeOIs such that any OI in the list matches an OI in the Roaming Consortium
193      * advertised by a hotspot operator. The list set by {@link #setMatchAllOis(long[])}
194      * will have precedence over this API, meaning this list will only be used for matching if the
195      * list set by {@link #setMatchAllOis(long[])} is null or empty.
196      *
197      * @param matchAnyOis An array of longs containing the HomeOIs
198      */
setMatchAnyOis(@ullable long[] matchAnyOis)199     public void setMatchAnyOis(@Nullable long[] matchAnyOis) {
200         mMatchAnyOis = matchAnyOis;
201     }
202 
203     /**
204      * Get a list of HomeOIs such that any OI in the list matches an OI in the Roaming Consortium
205      * advertised by a hotspot operator.
206      *
207      * @return An array of longs containing the HomeOIs
208      */
getMatchAnyOis()209     public @Nullable long[] getMatchAnyOis() {
210         return mMatchAnyOis;
211     }
212 
213     /**
214      * List of FQDN (Fully Qualified Domain Name) of partner providers.
215      * These providers should also be regarded as home Hotspot operators.
216      * This relationship is most likely achieved via a commercial agreement or
217      * operator merges between the providers.
218      */
219     private String[] mOtherHomePartners = null;
220 
221     /**
222      * Set the list of FQDN (Fully Qualified Domain Name) of other Home partner providers.
223      *
224      * @param otherHomePartners Array of Strings containing the FQDNs of other Home partner
225      *                         providers
226      * @hide
227      */
setOtherHomePartners(@ullable String[] otherHomePartners)228     public void setOtherHomePartners(@Nullable String[] otherHomePartners) {
229         mOtherHomePartners = otherHomePartners;
230     }
231 
232     /**
233      * Set the list of FQDN (Fully Qualified Domain Name) of other Home partner providers.
234      *
235      * @param otherHomePartners Collection of Strings containing the FQDNs of other Home partner
236      *                         providers
237      */
setOtherHomePartnersList(@onNull Collection<String> otherHomePartners)238     public void setOtherHomePartnersList(@NonNull Collection<String> otherHomePartners) {
239         if (otherHomePartners == null) {
240             return;
241         }
242         mOtherHomePartners = otherHomePartners.toArray(new String[otherHomePartners.size()]);
243     }
244 
245     /**
246      * Get the list of FQDN (Fully Qualified Domain Name) of other Home partner providers set in
247      * the profile.
248      *
249      * @return Array of Strings containing the FQDNs of other Home partner providers set in the
250      * profile
251      * @hide
252      */
getOtherHomePartners()253     public @Nullable String[] getOtherHomePartners() {
254         return mOtherHomePartners;
255     }
256 
257     /**
258      * Get the list of FQDN (Fully Qualified Domain Name) of other Home partner providers set in
259      * the profile.
260      *
261      * @return Collection of Strings containing the FQDNs of other Home partner providers set in the
262      * profile
263      */
getOtherHomePartnersList()264     public @NonNull Collection<String> getOtherHomePartnersList() {
265         if (mOtherHomePartners == null) {
266             return Collections.emptyList();
267         }
268         return Arrays.asList(mOtherHomePartners);
269     }
270 
271     /**
272      * List of Organization Identifiers (OIs) identifying a roaming consortium of
273      * which this provider is a member.
274      */
275     private long[] mRoamingConsortiumOis = null;
276     /**
277      * Set the Organization Identifiers (OIs) identifying a roaming consortium of which this
278      * provider is a member.
279      *
280      * @param roamingConsortiumOis Array of roaming consortium OIs
281      */
setRoamingConsortiumOis(long[] roamingConsortiumOis)282     public void setRoamingConsortiumOis(long[] roamingConsortiumOis) {
283         mRoamingConsortiumOis = roamingConsortiumOis;
284     }
285     /**
286      * Get the Organization Identifiers (OIs) identifying a roaming consortium of which this
287      * provider is a member.
288      *
289      * @return array of roaming consortium OIs
290      */
getRoamingConsortiumOis()291     public long[] getRoamingConsortiumOis() {
292         return mRoamingConsortiumOis;
293     }
294 
295     /**
296      * Constructor for creating HomeSp with default values.
297      */
HomeSp()298     public HomeSp() {}
299 
300     /**
301      * Copy constructor.
302      *
303      * @param source The source to copy from
304      */
HomeSp(HomeSp source)305     public HomeSp(HomeSp source) {
306         if (source == null) {
307             return;
308         }
309         mFqdn = source.mFqdn;
310         mFriendlyName = source.mFriendlyName;
311         mIconUrl = source.mIconUrl;
312         if (source.mHomeNetworkIds != null) {
313             mHomeNetworkIds = Collections.unmodifiableMap(source.mHomeNetworkIds);
314         }
315         if (source.mMatchAllOis != null) {
316             mMatchAllOis = Arrays.copyOf(source.mMatchAllOis, source.mMatchAllOis.length);
317         }
318         if (source.mMatchAnyOis != null) {
319             mMatchAnyOis = Arrays.copyOf(source.mMatchAnyOis, source.mMatchAnyOis.length);
320         }
321         if (source.mOtherHomePartners != null) {
322             mOtherHomePartners = Arrays.copyOf(source.mOtherHomePartners,
323                     source.mOtherHomePartners.length);
324         }
325         if (source.mRoamingConsortiumOis != null) {
326             mRoamingConsortiumOis = Arrays.copyOf(source.mRoamingConsortiumOis,
327                     source.mRoamingConsortiumOis.length);
328         }
329     }
330 
331     @Override
describeContents()332     public int describeContents() {
333         return 0;
334     }
335 
336     @Override
writeToParcel(Parcel dest, int flags)337     public void writeToParcel(Parcel dest, int flags) {
338         dest.writeString(mFqdn);
339         dest.writeString(mFriendlyName);
340         dest.writeString(mIconUrl);
341         writeHomeNetworkIds(dest, mHomeNetworkIds);
342         dest.writeLongArray(mMatchAllOis);
343         dest.writeLongArray(mMatchAnyOis);
344         dest.writeStringArray(mOtherHomePartners);
345         dest.writeLongArray(mRoamingConsortiumOis);
346     }
347 
348     @Override
equals(Object thatObject)349     public boolean equals(Object thatObject) {
350         if (this == thatObject) {
351             return true;
352         }
353         if (!(thatObject instanceof HomeSp)) {
354             return false;
355         }
356         HomeSp that = (HomeSp) thatObject;
357 
358         return TextUtils.equals(mFqdn, that.mFqdn)
359                 && TextUtils.equals(mFriendlyName, that.mFriendlyName)
360                 && TextUtils.equals(mIconUrl, that.mIconUrl)
361                 && (mHomeNetworkIds == null ? that.mHomeNetworkIds == null
362                         : mHomeNetworkIds.equals(that.mHomeNetworkIds))
363                 && Arrays.equals(mMatchAllOis, that.mMatchAllOis)
364                 && Arrays.equals(mMatchAnyOis, that.mMatchAnyOis)
365                 && Arrays.equals(mOtherHomePartners, that.mOtherHomePartners)
366                 && Arrays.equals(mRoamingConsortiumOis, that.mRoamingConsortiumOis);
367     }
368 
369     @Override
hashCode()370     public int hashCode() {
371         return Objects.hash(mFqdn, mFriendlyName, mIconUrl,
372                 mHomeNetworkIds, Arrays.hashCode(mMatchAllOis),
373                 Arrays.hashCode(mMatchAnyOis), Arrays.hashCode(mOtherHomePartners),
374                 Arrays.hashCode(mRoamingConsortiumOis));
375     }
376 
377     /**
378      * Get a unique identifier for HomeSp. This identifier depends only on items that remain
379      * constant throughout the lifetime of a subscription.
380      *
381      * @hide
382      * @return a Unique identifier for a HomeSp object
383      */
getUniqueId()384     public int getUniqueId() {
385         return Objects.hash(mFqdn);
386     }
387 
388 
389     @Override
toString()390     public String toString() {
391         StringBuilder builder = new StringBuilder();
392         builder.append("FQDN: ").append(mFqdn).append("\n");
393         builder.append("FriendlyName: ").append(mFriendlyName).append("\n");
394         builder.append("IconURL: ").append(mIconUrl).append("\n");
395         builder.append("HomeNetworkIDs: ").append(mHomeNetworkIds).append("\n");
396         builder.append("MatchAllOIs: ").append(Arrays.toString(mMatchAllOis)).append("\n");
397         builder.append("MatchAnyOIs: ").append(Arrays.toString(mMatchAnyOis)).append("\n");
398         builder.append("OtherHomePartners: ").append(Arrays.toString(mOtherHomePartners))
399                 .append("\n");
400         builder.append("RoamingConsortiumOIs: ").append(Arrays.toString(mRoamingConsortiumOis))
401                 .append("\n");
402         return builder.toString();
403     }
404 
405     /**
406      * Validate HomeSp data.
407      *
408      * @return true on success or false on failure
409      * @hide
410      */
validate()411     public boolean validate() {
412         if (TextUtils.isEmpty(mFqdn)) {
413             Log.d(TAG, "Missing FQDN");
414             return false;
415         }
416         if (mFqdn.getBytes(StandardCharsets.UTF_8).length > MAX_STRING_LENGTH) {
417             Log.d(TAG, "FQDN is too long");
418             return false;
419         }
420         if (TextUtils.isEmpty(mFriendlyName)) {
421             Log.d(TAG, "Missing friendly name");
422             return false;
423         }
424         if (mFriendlyName.getBytes(StandardCharsets.UTF_8).length > MAX_STRING_LENGTH) {
425             Log.d(TAG, "Friendly name is too long");
426             return false;
427         }
428         // Verify SSIDs specified in the NetworkID
429         if (mHomeNetworkIds != null) {
430             if (mHomeNetworkIds.size() > MAX_NUMBER_OF_ENTRIES) {
431                 Log.d(TAG, "too many SSID in HomeNetworkIDs");
432                 return false;
433             }
434             for (Map.Entry<String, Long> entry : mHomeNetworkIds.entrySet()) {
435                 if (entry.getKey() == null ||
436                         entry.getKey().getBytes(StandardCharsets.UTF_8).length > MAX_SSID_BYTES) {
437                     Log.d(TAG, "SSID is too long in HomeNetworkIDs");
438                     return false;
439                 }
440                 if (entry.getValue() != null
441                         && (entry.getValue() > MAX_HESSID_VALUE || entry.getValue() < 0)) {
442                     Log.d(TAG, "HESSID is out of range");
443                     return false;
444                 }
445             }
446         }
447         if (mIconUrl != null && mIconUrl.getBytes(StandardCharsets.UTF_8).length > MAX_URL_BYTES) {
448             Log.d(TAG, "Icon URL is too long");
449             return false;
450         }
451         if (mMatchAllOis != null) {
452             if (mMatchAllOis.length > MAX_NUMBER_OF_OI) {
453                 Log.d(TAG, "too many match all Organization Identifiers in the profile");
454                 return false;
455             }
456             for (long oi : mMatchAllOis) {
457                 if (oi > MAX_OI_VALUE || oi < 0) {
458                     Log.d(TAG, "Organization Identifiers is out of range");
459                     return false;
460                 }
461             }
462         }
463         if (mMatchAnyOis != null) {
464             if (mMatchAnyOis.length > MAX_NUMBER_OF_OI) {
465                 Log.d(TAG, "too many match any Organization Identifiers in the profile");
466                 return false;
467             }
468             for (long oi : mMatchAnyOis) {
469                 if (oi > MAX_OI_VALUE || oi < 0) {
470                     Log.d(TAG, "Organization Identifiers is out of range");
471                     return false;
472                 }
473             }
474         }
475         if (mRoamingConsortiumOis != null) {
476             if (mRoamingConsortiumOis.length > MAX_NUMBER_OF_OI) {
477                 Log.d(TAG, "too many Roaming Consortium Organization Identifiers in the "
478                         + "profile");
479                 return false;
480             }
481             for (long oi : mRoamingConsortiumOis) {
482                 if (oi > MAX_OI_VALUE || oi < 0) {
483                     Log.d(TAG, "Organization Identifiers is out of range");
484                     return false;
485                 }
486             }
487         }
488         if (mOtherHomePartners != null) {
489             if (mOtherHomePartners.length > MAX_NUMBER_OF_ENTRIES) {
490                 Log.d(TAG, "too many other home partners in the profile");
491                 return false;
492             }
493             for (String fqdn : mOtherHomePartners) {
494                 if (fqdn.length() > MAX_STRING_LENGTH) {
495                     Log.d(TAG, "FQDN is too long in OtherHomePartners");
496                     return false;
497                 }
498             }
499         }
500         return true;
501     }
502 
503     public static final @android.annotation.NonNull Creator<HomeSp> CREATOR =
504         new Creator<HomeSp>() {
505             @Override
506             public HomeSp createFromParcel(Parcel in) {
507                 HomeSp homeSp = new HomeSp();
508                 homeSp.setFqdn(in.readString());
509                 homeSp.setFriendlyName(in.readString());
510                 homeSp.setIconUrl(in.readString());
511                 homeSp.setHomeNetworkIds(readHomeNetworkIds(in));
512                 homeSp.setMatchAllOis(in.createLongArray());
513                 homeSp.setMatchAnyOis(in.createLongArray());
514                 homeSp.setOtherHomePartners(in.createStringArray());
515                 homeSp.setRoamingConsortiumOis(in.createLongArray());
516                 return homeSp;
517             }
518 
519             @Override
520             public HomeSp[] newArray(int size) {
521                 return new HomeSp[size];
522             }
523 
524             /**
525              * Helper function for reading a Home Network IDs map from a Parcel.
526              *
527              * @param in The Parcel to read from
528              * @return Map of home network IDs
529              */
530             private Map<String, Long> readHomeNetworkIds(Parcel in) {
531                 int size = in.readInt();
532                 if (size == NULL_VALUE) {
533                     return null;
534                 }
535                 Map<String, Long> networkIds = new HashMap<>(size);
536                 for (int i = 0; i < size; i++) {
537                     String key = in.readString();
538                     Long value = null;
539                     long readValue = in.readLong();
540                     if (readValue != NULL_VALUE) {
541                         value = Long.valueOf(readValue);
542                     }
543                     networkIds.put(key, value);
544                 }
545                 return networkIds;
546             }
547         };
548 
549     /**
550      * Helper function for writing Home Network IDs map to a Parcel.
551      *
552      * @param dest The Parcel to write to
553      * @param networkIds The map of home network IDs
554      */
writeHomeNetworkIds(Parcel dest, Map<String, Long> networkIds)555     private static void writeHomeNetworkIds(Parcel dest, Map<String, Long> networkIds) {
556         if (networkIds == null) {
557             dest.writeInt(NULL_VALUE);
558             return;
559         }
560         dest.writeInt(networkIds.size());
561         for (Map.Entry<String, Long> entry : networkIds.entrySet()) {
562             dest.writeString(entry.getKey());
563             if (entry.getValue() == null) {
564                 dest.writeLong(NULL_VALUE);
565             } else {
566                 dest.writeLong(entry.getValue());
567             }
568         }
569     }
570 }
571