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