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