blob: b0a2cc397c53bb9649572e742c0379531384616e [file] [log] [blame]
/**
* Copyright (c) 2017, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net.wifi.hotspot2.pps;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Log;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* Class representing Policy subtree in PerProviderSubscription (PPS)
* Management Object (MO) tree.
*
* The Policy specifies additional criteria for Passpoint network selections, such as preferred
* roaming partner, minimum backhaul bandwidth, and etc. It also provides the meta data for
* updating the policy.
*
* For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
* Release 2 Technical Specification.
*
* @hide
*/
public final class Policy implements Parcelable {
private static final String TAG = "Policy";
/**
* Maximum number of SSIDs in the exclusion list.
*/
private static final int MAX_EXCLUSION_SSIDS = 128;
/**
* Maximum byte for SSID.
*/
private static final int MAX_SSID_BYTES = 32;
/**
* Maximum bytes for port string in {@link #requiredProtoPortMap}.
*/
private static final int MAX_PORT_STRING_BYTES = 64;
/**
* Integer value used for indicating null value in the Parcel.
*/
private static final int NULL_VALUE = -1;
/**
* Minimum available downlink/uplink bandwidth (in kilobits per second) required when
* selecting a network from home providers.
*
* The bandwidth is calculated as the LinkSpeed * (1 – LinkLoad/255), where LinkSpeed
* and LinkLoad parameters are drawn from the WAN Metrics ANQP element at that hotspot.
*
* Using Long.MIN_VALUE to indicate unset value.
*/
private long mMinHomeDownlinkBandwidth = Long.MIN_VALUE;
public void setMinHomeDownlinkBandwidth(long minHomeDownlinkBandwidth) {
mMinHomeDownlinkBandwidth = minHomeDownlinkBandwidth;
}
public long getMinHomeDownlinkBandwidth() {
return mMinHomeDownlinkBandwidth;
}
private long mMinHomeUplinkBandwidth = Long.MIN_VALUE;
public void setMinHomeUplinkBandwidth(long minHomeUplinkBandwidth) {
mMinHomeUplinkBandwidth = minHomeUplinkBandwidth;
}
public long getMinHomeUplinkBandwidth() {
return mMinHomeUplinkBandwidth;
}
/**
* Minimum available downlink/uplink bandwidth (in kilobits per second) required when
* selecting a network from roaming providers.
*
* The bandwidth is calculated as the LinkSpeed * (1 – LinkLoad/255), where LinkSpeed
* and LinkLoad parameters are drawn from the WAN Metrics ANQP element at that hotspot.
*
* Using Long.MIN_VALUE to indicate unset value.
*/
private long mMinRoamingDownlinkBandwidth = Long.MIN_VALUE;
public void setMinRoamingDownlinkBandwidth(long minRoamingDownlinkBandwidth) {
mMinRoamingDownlinkBandwidth = minRoamingDownlinkBandwidth;
}
public long getMinRoamingDownlinkBandwidth() {
return mMinRoamingDownlinkBandwidth;
}
private long mMinRoamingUplinkBandwidth = Long.MIN_VALUE;
public void setMinRoamingUplinkBandwidth(long minRoamingUplinkBandwidth) {
mMinRoamingUplinkBandwidth = minRoamingUplinkBandwidth;
}
public long getMinRoamingUplinkBandwidth() {
return mMinRoamingUplinkBandwidth;
}
/**
* List of SSIDs that are not preferred by the Home SP.
*/
private String[] mExcludedSsidList = null;
public void setExcludedSsidList(String[] excludedSsidList) {
mExcludedSsidList = excludedSsidList;
}
public String[] getExcludedSsidList() {
return mExcludedSsidList;
}
/**
* List of IP protocol and port number required by one or more operator supported application.
* The port string contained one or more port numbers delimited by ",".
*/
private Map<Integer, String> mRequiredProtoPortMap = null;
public void setRequiredProtoPortMap(Map<Integer, String> requiredProtoPortMap) {
mRequiredProtoPortMap = requiredProtoPortMap;
}
public Map<Integer, String> getRequiredProtoPortMap() {
return mRequiredProtoPortMap;
}
/**
* This specifies the maximum acceptable BSS load policy. This is used to prevent device
* from joining an AP whose channel is overly congested with traffic.
* Using Integer.MIN_VALUE to indicate unset value.
*/
private int mMaximumBssLoadValue = Integer.MIN_VALUE;
public void setMaximumBssLoadValue(int maximumBssLoadValue) {
mMaximumBssLoadValue = maximumBssLoadValue;
}
public int getMaximumBssLoadValue() {
return mMaximumBssLoadValue;
}
/**
* Policy associated with a roaming provider. This specifies a priority associated
* with a roaming provider for given list of countries.
*
* Contains field under PerProviderSubscription/Policy/PreferredRoamingPartnerList.
*/
public static final class RoamingPartner implements Parcelable {
/**
* FQDN of the roaming partner.
*/
private String mFqdn = null;
public void setFqdn(String fqdn) {
mFqdn = fqdn;
}
public String getFqdn() {
return mFqdn;
}
/**
* Flag indicating the exact match of FQDN is required for FQDN matching.
*
* When this flag is set to false, sub-domain matching is used. For example, when
* {@link #fqdn} s set to "example.com", "host.example.com" would be a match.
*/
private boolean mFqdnExactMatch = false;
public void setFqdnExactMatch(boolean fqdnExactMatch) {
mFqdnExactMatch = fqdnExactMatch;
}
public boolean getFqdnExactMatch() {
return mFqdnExactMatch;
}
/**
* Priority associated with this roaming partner policy.
* Using Integer.MIN_VALUE to indicate unset value.
*/
private int mPriority = Integer.MIN_VALUE;
public void setPriority(int priority) {
mPriority = priority;
}
public int getPriority() {
return mPriority;
}
/**
* A string contained One or more, comma delimited (i.e., ",") ISO/IEC 3166-1 two
* character country strings or the country-independent value, "*".
*/
private String mCountries = null;
public void setCountries(String countries) {
mCountries = countries;
}
public String getCountries() {
return mCountries;
}
public RoamingPartner() {}
public RoamingPartner(RoamingPartner source) {
if (source != null) {
mFqdn = source.mFqdn;
mFqdnExactMatch = source.mFqdnExactMatch;
mPriority = source.mPriority;
mCountries = source.mCountries;
}
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mFqdn);
dest.writeInt(mFqdnExactMatch ? 1 : 0);
dest.writeInt(mPriority);
dest.writeString(mCountries);
}
@Override
public boolean equals(Object thatObject) {
if (this == thatObject) {
return true;
}
if (!(thatObject instanceof RoamingPartner)) {
return false;
}
RoamingPartner that = (RoamingPartner) thatObject;
return TextUtils.equals(mFqdn, that.mFqdn)
&& mFqdnExactMatch == that.mFqdnExactMatch
&& mPriority == that.mPriority
&& TextUtils.equals(mCountries, that.mCountries);
}
@Override
public int hashCode() {
return Objects.hash(mFqdn, mFqdnExactMatch, mPriority, mCountries);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("FQDN: ").append(mFqdn).append("\n");
builder.append("ExactMatch: ").append("mFqdnExactMatch").append("\n");
builder.append("Priority: ").append(mPriority).append("\n");
builder.append("Countries: ").append(mCountries).append("\n");
return builder.toString();
}
/**
* Validate RoamingParnter data.
*
* @return true on success
* @hide
*/
public boolean validate() {
if (TextUtils.isEmpty(mFqdn)) {
Log.d(TAG, "Missing FQDN");
return false;
}
if (TextUtils.isEmpty(mCountries)) {
Log.d(TAG, "Missing countries");
return false;
}
return true;
}
public static final @android.annotation.NonNull Creator<RoamingPartner> CREATOR =
new Creator<RoamingPartner>() {
@Override
public RoamingPartner createFromParcel(Parcel in) {
RoamingPartner roamingPartner = new RoamingPartner();
roamingPartner.setFqdn(in.readString());
roamingPartner.setFqdnExactMatch(in.readInt() != 0);
roamingPartner.setPriority(in.readInt());
roamingPartner.setCountries(in.readString());
return roamingPartner;
}
@Override
public RoamingPartner[] newArray(int size) {
return new RoamingPartner[size];
}
};
}
private List<RoamingPartner> mPreferredRoamingPartnerList = null;
public void setPreferredRoamingPartnerList(List<RoamingPartner> partnerList) {
mPreferredRoamingPartnerList = partnerList;
}
public List<RoamingPartner> getPreferredRoamingPartnerList() {
return mPreferredRoamingPartnerList;
}
/**
* Meta data used for policy update.
*/
private UpdateParameter mPolicyUpdate = null;
public void setPolicyUpdate(UpdateParameter policyUpdate) {
mPolicyUpdate = policyUpdate;
}
public UpdateParameter getPolicyUpdate() {
return mPolicyUpdate;
}
/**
* Constructor for creating Policy with default values.
*/
public Policy() {}
/**
* Copy constructor.
*
* @param source The source to copy from
*/
public Policy(Policy source) {
if (source == null) {
return;
}
mMinHomeDownlinkBandwidth = source.mMinHomeDownlinkBandwidth;
mMinHomeUplinkBandwidth = source.mMinHomeUplinkBandwidth;
mMinRoamingDownlinkBandwidth = source.mMinRoamingDownlinkBandwidth;
mMinRoamingUplinkBandwidth = source.mMinRoamingUplinkBandwidth;
mMaximumBssLoadValue = source.mMaximumBssLoadValue;
if (source.mExcludedSsidList != null) {
mExcludedSsidList = Arrays.copyOf(source.mExcludedSsidList,
source.mExcludedSsidList.length);
}
if (source.mRequiredProtoPortMap != null) {
mRequiredProtoPortMap = Collections.unmodifiableMap(source.mRequiredProtoPortMap);
}
if (source.mPreferredRoamingPartnerList != null) {
mPreferredRoamingPartnerList = Collections.unmodifiableList(
source.mPreferredRoamingPartnerList);
}
if (source.mPolicyUpdate != null) {
mPolicyUpdate = new UpdateParameter(source.mPolicyUpdate);
}
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(mMinHomeDownlinkBandwidth);
dest.writeLong(mMinHomeUplinkBandwidth);
dest.writeLong(mMinRoamingDownlinkBandwidth);
dest.writeLong(mMinRoamingUplinkBandwidth);
dest.writeStringArray(mExcludedSsidList);
writeProtoPortMap(dest, mRequiredProtoPortMap);
dest.writeInt(mMaximumBssLoadValue);
writeRoamingPartnerList(dest, flags, mPreferredRoamingPartnerList);
dest.writeParcelable(mPolicyUpdate, flags);
}
@Override
public boolean equals(Object thatObject) {
if (this == thatObject) {
return true;
}
if (!(thatObject instanceof Policy)) {
return false;
}
Policy that = (Policy) thatObject;
return mMinHomeDownlinkBandwidth == that.mMinHomeDownlinkBandwidth
&& mMinHomeUplinkBandwidth == that.mMinHomeUplinkBandwidth
&& mMinRoamingDownlinkBandwidth == that.mMinRoamingDownlinkBandwidth
&& mMinRoamingUplinkBandwidth == that.mMinRoamingUplinkBandwidth
&& Arrays.equals(mExcludedSsidList, that.mExcludedSsidList)
&& (mRequiredProtoPortMap == null ? that.mRequiredProtoPortMap == null
: mRequiredProtoPortMap.equals(that.mRequiredProtoPortMap))
&& mMaximumBssLoadValue == that.mMaximumBssLoadValue
&& (mPreferredRoamingPartnerList == null
? that.mPreferredRoamingPartnerList == null
: mPreferredRoamingPartnerList.equals(that.mPreferredRoamingPartnerList))
&& (mPolicyUpdate == null ? that.mPolicyUpdate == null
: mPolicyUpdate.equals(that.mPolicyUpdate));
}
@Override
public int hashCode() {
return Objects.hash(mMinHomeDownlinkBandwidth, mMinHomeUplinkBandwidth,
mMinRoamingDownlinkBandwidth, mMinRoamingUplinkBandwidth, mExcludedSsidList,
mRequiredProtoPortMap, mMaximumBssLoadValue, mPreferredRoamingPartnerList,
mPolicyUpdate);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("MinHomeDownlinkBandwidth: ").append(mMinHomeDownlinkBandwidth)
.append("\n");
builder.append("MinHomeUplinkBandwidth: ").append(mMinHomeUplinkBandwidth).append("\n");
builder.append("MinRoamingDownlinkBandwidth: ").append(mMinRoamingDownlinkBandwidth)
.append("\n");
builder.append("MinRoamingUplinkBandwidth: ").append(mMinRoamingUplinkBandwidth)
.append("\n");
builder.append("ExcludedSSIDList: ").append(mExcludedSsidList).append("\n");
builder.append("RequiredProtoPortMap: ").append(mRequiredProtoPortMap).append("\n");
builder.append("MaximumBSSLoadValue: ").append(mMaximumBssLoadValue).append("\n");
builder.append("PreferredRoamingPartnerList: ").append(mPreferredRoamingPartnerList)
.append("\n");
if (mPolicyUpdate != null) {
builder.append("PolicyUpdate Begin ---\n");
builder.append(mPolicyUpdate);
builder.append("PolicyUpdate End ---\n");
}
return builder.toString();
}
/**
* Validate Policy data.
*
* @return true on success
* @hide
*/
public boolean validate() {
if (mPolicyUpdate == null) {
Log.d(TAG, "PolicyUpdate not specified");
return false;
}
if (!mPolicyUpdate.validate()) {
return false;
}
// Validate SSID exclusion list.
if (mExcludedSsidList != null) {
if (mExcludedSsidList.length > MAX_EXCLUSION_SSIDS) {
Log.d(TAG, "SSID exclusion list size exceeded the max: "
+ mExcludedSsidList.length);
return false;
}
for (String ssid : mExcludedSsidList) {
if (ssid.getBytes(StandardCharsets.UTF_8).length > MAX_SSID_BYTES) {
Log.d(TAG, "Invalid SSID: " + ssid);
return false;
}
}
}
// Validate required protocol to port map.
if (mRequiredProtoPortMap != null) {
for (Map.Entry<Integer, String> entry : mRequiredProtoPortMap.entrySet()) {
String portNumber = entry.getValue();
if (portNumber.getBytes(StandardCharsets.UTF_8).length > MAX_PORT_STRING_BYTES) {
Log.d(TAG, "PortNumber string bytes exceeded the max: " + portNumber);
return false;
}
}
}
// Validate preferred roaming partner list.
if (mPreferredRoamingPartnerList != null) {
for (RoamingPartner partner : mPreferredRoamingPartnerList) {
if (!partner.validate()) {
return false;
}
}
}
return true;
}
public static final @android.annotation.NonNull Creator<Policy> CREATOR =
new Creator<Policy>() {
@Override
public Policy createFromParcel(Parcel in) {
Policy policy = new Policy();
policy.setMinHomeDownlinkBandwidth(in.readLong());
policy.setMinHomeUplinkBandwidth(in.readLong());
policy.setMinRoamingDownlinkBandwidth(in.readLong());
policy.setMinRoamingUplinkBandwidth(in.readLong());
policy.setExcludedSsidList(in.createStringArray());
policy.setRequiredProtoPortMap(readProtoPortMap(in));
policy.setMaximumBssLoadValue(in.readInt());
policy.setPreferredRoamingPartnerList(readRoamingPartnerList(in));
policy.setPolicyUpdate(in.readParcelable(null));
return policy;
}
@Override
public Policy[] newArray(int size) {
return new Policy[size];
}
/**
* Helper function for reading IP Protocol to Port Number map from a Parcel.
*
* @param in The Parcel to read from
* @return Map of IP protocol to port number
*/
private Map<Integer, String> readProtoPortMap(Parcel in) {
int size = in.readInt();
if (size == NULL_VALUE) {
return null;
}
Map<Integer, String> protoPortMap = new HashMap<>(size);
for (int i = 0; i < size; i++) {
int key = in.readInt();
String value = in.readString();
protoPortMap.put(key, value);
}
return protoPortMap;
}
/**
* Helper function for reading roaming partner list from a Parcel.
*
* @param in The Parcel to read from
* @return List of roaming partners
*/
private List<RoamingPartner> readRoamingPartnerList(Parcel in) {
int size = in.readInt();
if (size == NULL_VALUE) {
return null;
}
List<RoamingPartner> partnerList = new ArrayList<>();
for (int i = 0; i < size; i++) {
partnerList.add(in.readParcelable(null));
}
return partnerList;
}
};
/**
* Helper function for writing IP Protocol to Port Number map to a Parcel.
*
* @param dest The Parcel to write to
* @param protoPortMap The map to write
*/
private static void writeProtoPortMap(Parcel dest, Map<Integer, String> protoPortMap) {
if (protoPortMap == null) {
dest.writeInt(NULL_VALUE);
return;
}
dest.writeInt(protoPortMap.size());
for (Map.Entry<Integer, String> entry : protoPortMap.entrySet()) {
dest.writeInt(entry.getKey());
dest.writeString(entry.getValue());
}
}
/**
* Helper function for writing roaming partner list to a Parcel.
*
* @param dest The Parcel to write to
* @param flags The flag about how the object should be written
* @param partnerList The partner list to write
*/
private static void writeRoamingPartnerList(Parcel dest, int flags,
List<RoamingPartner> partnerList) {
if (partnerList == null) {
dest.writeInt(NULL_VALUE);
return;
}
dest.writeInt(partnerList.size());
for (RoamingPartner partner : partnerList) {
dest.writeParcelable(partner, flags);
}
}
}