/* * Copyright (C) 2023 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; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.net.DscpPolicy; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; import androidx.annotation.RequiresApi; import com.android.modules.utils.build.SdkLevel; import com.android.wifi.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.util.Arrays; import java.util.Objects; /** * Parameters for QoS policies requested by system applications. * @hide */ @SystemApi public final class QosPolicyParams implements Parcelable { private static final String TAG = "QosPolicyParams"; /** * Indicates that the policy does not specify a DSCP value. */ public static final int DSCP_ANY = -1; /** * Indicates that the policy does not specify a protocol. */ public static final int PROTOCOL_ANY = DscpPolicy.PROTOCOL_ANY; /** * Policy should match packets using the TCP protocol. */ public static final int PROTOCOL_TCP = 6; /** * Policy should match packets using the UDP protocol. */ public static final int PROTOCOL_UDP = 17; /** * Policy should match packets using the ESP protocol. */ public static final int PROTOCOL_ESP = 50; /** @hide */ @IntDef(prefix = { "PROTOCOL_" }, value = { PROTOCOL_ANY, PROTOCOL_TCP, PROTOCOL_UDP, PROTOCOL_ESP }) @Retention(RetentionPolicy.SOURCE) public @interface Protocol {} /** * Policy should match packets in the uplink direction. */ public static final int DIRECTION_UPLINK = 0; /** * Policy should match packets in the downlink direction. */ public static final int DIRECTION_DOWNLINK = 1; /** @hide */ @IntDef(prefix = { "DIRECTION_" }, value = { DIRECTION_UPLINK, DIRECTION_DOWNLINK, }) @Retention(RetentionPolicy.SOURCE) public @interface Direction {} /** * Indicates that the policy does not specify a User Priority. */ public static final int USER_PRIORITY_ANY = -1; /** * Policy should be assigned a low background priority. */ public static final int USER_PRIORITY_BACKGROUND_LOW = 1; /** * Policy should be assigned a high background priority. */ public static final int USER_PRIORITY_BACKGROUND_HIGH = 2; /** * Policy should be assigned a low best-effort priority. */ public static final int USER_PRIORITY_BEST_EFFORT_LOW = 0; /** * Policy should be assigned a high best-effort priority. */ public static final int USER_PRIORITY_BEST_EFFORT_HIGH = 3; /** * Policy should be assigned a low video priority. */ public static final int USER_PRIORITY_VIDEO_LOW = 4; /** * Policy should be assigned a high video priority. */ public static final int USER_PRIORITY_VIDEO_HIGH = 5; /** * Policy should be assigned a low voice priority. */ public static final int USER_PRIORITY_VOICE_LOW = 6; /** * Policy should be assigned a high voice priority. */ public static final int USER_PRIORITY_VOICE_HIGH = 7; /** @hide */ @IntDef(prefix = { "USER_PRIORITY_" }, value = { USER_PRIORITY_ANY, USER_PRIORITY_BACKGROUND_LOW, USER_PRIORITY_BACKGROUND_HIGH, USER_PRIORITY_BEST_EFFORT_LOW, USER_PRIORITY_BEST_EFFORT_HIGH, USER_PRIORITY_VIDEO_LOW, USER_PRIORITY_VIDEO_HIGH, USER_PRIORITY_VOICE_LOW, USER_PRIORITY_VOICE_HIGH, }) @Retention(RetentionPolicy.SOURCE) public @interface UserPriority {} /** * Indicates that the policy does not specify an IP version. */ public static final int IP_VERSION_ANY = -1; /** * Policy should match packets using IPv4. */ public static final int IP_VERSION_4 = 4; /** * Policy should match packets using IPv6. */ public static final int IP_VERSION_6 = 6; /** @hide */ @IntDef(prefix = { "IP_VERSION_" }, value = { IP_VERSION_ANY, IP_VERSION_4, IP_VERSION_6 }) @Retention(RetentionPolicy.SOURCE) public @interface IpVersion {} /** * Indicates that the policy does not specify a destination port. */ public static final int DESTINATION_PORT_ANY = -1; /** * Unique policy ID. See {@link Builder#Builder(int, int)} for more information. */ private final int mPolicyId; /** * Translated policy ID. Should only be set by the Wi-Fi service. * @hide */ private int mTranslatedPolicyId; // QoS DSCP marking. See {@link Builder#setDscp(int)} for more information. private final int mDscp; // User priority to apply to packets matching the policy. Only applicable to downlink requests. private final int mUserPriority; // Source IP address. private final @Nullable InetAddress mSrcIp; // Destination IP address. private final @Nullable InetAddress mDstIp; // Source port. private final int mSrcPort; // IP protocol that the policy requires. private final @Protocol int mProtocol; // Single destination port. Only applicable to downlink requests. private final int mDstPort; // Destination port range. Inclusive range. Only applicable to uplink requests. private final @Nullable int[] mDstPortRange; // Direction of traffic stream. private final @Direction int mDirection; // IP version. Only applicable to downlink requests. private final @IpVersion int mIpVersion; // Flow label. Only applicable to downlink requests using IPv6. private final @Nullable byte[] mFlowLabel; // QoS characteristics. Mandatory for uplink requests. private final @Nullable QosCharacteristics mQosCharacteristics; private QosPolicyParams(int policyId, int dscp, @UserPriority int userPriority, @Nullable InetAddress srcIp, @Nullable InetAddress dstIp, int srcPort, @Protocol int protocol, @Nullable int[] dstPortRange, @Direction int direction, @IpVersion int ipVersion, int dstPort, @Nullable byte[] flowLabel, @Nullable QosCharacteristics qosCharacteristics) { this.mPolicyId = policyId; this.mDscp = dscp; this.mUserPriority = userPriority; this.mSrcIp = srcIp; this.mDstIp = dstIp; this.mSrcPort = srcPort; this.mProtocol = protocol; this.mDstPort = dstPort; this.mDstPortRange = dstPortRange; this.mDirection = direction; this.mIpVersion = ipVersion; this.mFlowLabel = flowLabel; this.mQosCharacteristics = qosCharacteristics; } /** * Validate the parameters in this instance. * * @return true if all parameters are valid, false otherwise * @hide */ public boolean validate() { if (mPolicyId < 1 || mPolicyId > 255) { Log.e(TAG, "Policy ID not in valid range: " + mPolicyId); return false; } if (mDscp < DSCP_ANY || mDscp > 63) { Log.e(TAG, "DSCP value not in valid range: " + mDscp); return false; } if (mUserPriority < USER_PRIORITY_ANY || mUserPriority > USER_PRIORITY_VOICE_HIGH) { Log.e(TAG, "User priority not in valid range: " + mUserPriority); return false; } if (mSrcPort < DscpPolicy.SOURCE_PORT_ANY || mSrcPort > 65535) { Log.e(TAG, "Source port not in valid range: " + mSrcPort); return false; } if (mDstPort < DESTINATION_PORT_ANY || mDstPort > 65535) { Log.e(TAG, "Destination port not in valid range: " + mDstPort); return false; } if (mDstPortRange != null && (mDstPortRange[0] < 0 || mDstPortRange[0] > 65535 || mDstPortRange[1] < 0 || mDstPortRange[1] > 65535)) { Log.e(TAG, "Dst port range value not valid. start=" + mDstPortRange[0] + ", end=" + mDstPortRange[1]); return false; } if (!(mDirection == DIRECTION_UPLINK || mDirection == DIRECTION_DOWNLINK)) { Log.e(TAG, "Invalid direction enum: " + mDirection); return false; } if (!(mIpVersion == IP_VERSION_ANY || mIpVersion == IP_VERSION_4 || mIpVersion == IP_VERSION_6)) { Log.e(TAG, "Invalid ipVersion enum: " + mIpVersion); return false; } if (mIpVersion == IP_VERSION_4) { if (mSrcIp != null && !(mSrcIp instanceof Inet4Address)) { Log.e(TAG, "Src address does not match IP version " + mIpVersion); return false; } if (mDstIp != null && !(mDstIp instanceof Inet4Address)) { Log.e(TAG, "Dst address does not match IP version " + mIpVersion); return false; } } if (mIpVersion == IP_VERSION_6) { if (mSrcIp != null && !(mSrcIp instanceof Inet6Address)) { Log.e(TAG, "Src address does not match IP version " + mIpVersion); return false; } if (mDstIp != null && !(mDstIp instanceof Inet6Address)) { Log.e(TAG, "Dst address does not match IP version " + mIpVersion); return false; } } if (mQosCharacteristics != null && !mQosCharacteristics.validate()) { Log.e(TAG, "Invalid QoS characteristics provided"); return false; } // Check required parameters based on direction. if (mDirection == DIRECTION_UPLINK) { if (mQosCharacteristics == null) { Log.e(TAG, "QoS characteristics must be provided for uplink requests"); return false; } if (mIpVersion != IP_VERSION_ANY) { Log.e(TAG, "IP Version should not be set for uplink requests"); return false; } if (mDstPort != DESTINATION_PORT_ANY) { Log.e(TAG, "Single destination port should not be set for uplink requests"); return false; } if (mFlowLabel != null) { Log.e(TAG, "Flow label should not be set for uplink requests"); return false; } } else { if (mUserPriority == USER_PRIORITY_ANY) { Log.e(TAG, "User priority must be provided for downlink requests"); return false; } if (mIpVersion == IP_VERSION_ANY) { Log.e(TAG, "IP version must be provided for downlink requests"); return false; } if (mDstPortRange != null) { Log.e(TAG, "Destination port range should not be set for downlink requests"); return false; } if (mFlowLabel != null) { if (mIpVersion != IP_VERSION_6) { Log.e(TAG, "Flow label can only be used with IP version 6"); return false; } if (mFlowLabel.length != 3) { Log.e(TAG, "Flow label must be of size 3, provided size is " + mFlowLabel.length); return false; } } } return true; } /** * Set the translated policy ID for this policy. * * Note: Translated policy IDs should only be set by the Wi-Fi service. * @hide */ public void setTranslatedPolicyId(int translatedPolicyId) { mTranslatedPolicyId = translatedPolicyId; } /** * Get the ID for this policy. * * See {@link Builder#Builder(int, int)} for more information. */ @IntRange(from = 1, to = 255) public int getPolicyId() { return mPolicyId; } /** * Get the translated ID for this policy. * * See {@link #setTranslatedPolicyId} for more information. * @hide */ public int getTranslatedPolicyId() { return mTranslatedPolicyId; } /** * Get the DSCP value for this policy. * * See {@link Builder#setDscp(int)} for more information. * * @return DSCP value, or {@link #DSCP_ANY} if not assigned. */ @IntRange(from = DSCP_ANY, to = 63) public int getDscp() { return mDscp; } /** * Get the User Priority (UP) for this policy. * * See {@link Builder#setUserPriority(int)} for more information. * * @return User Priority value, or {@link #USER_PRIORITY_ANY} if not assigned. */ public @UserPriority int getUserPriority() { return mUserPriority; } /** * Get the source IP address for this policy. * * See {@link Builder#setSourceAddress(InetAddress)} for more information. * * @return source IP address, or null if not assigned. */ public @Nullable InetAddress getSourceAddress() { return mSrcIp; } /** * Get the destination IP address for this policy. * * See {@link Builder#setDestinationAddress(InetAddress)} for more information. * * @return destination IP address, or null if not assigned. */ public @Nullable InetAddress getDestinationAddress() { return mDstIp; } /** * Get the source port for this policy. * * See {@link Builder#setSourcePort(int)} for more information. * * @return source port, or {@link DscpPolicy#SOURCE_PORT_ANY} if not assigned. */ @IntRange(from = DscpPolicy.SOURCE_PORT_ANY, to = 65535) public int getSourcePort() { return mSrcPort; } /** * Get the protocol for this policy. * * See {@link Builder#setProtocol(int)} for more information. * * @return protocol, or {@link #PROTOCOL_ANY} if not assigned. */ public @Protocol int getProtocol() { return mProtocol; } /** * Get the destination port for this policy. * * See {@link Builder#setDestinationPort(int)} for more information. * * @return destination port, or {@link #DESTINATION_PORT_ANY} if not assigned. */ @IntRange(from = DESTINATION_PORT_ANY, to = 65535) public int getDestinationPort() { return mDstPort; } /** * Get the destination port range for this policy. * * See {@link Builder#setDestinationPortRange(int, int)} for more information. * * @return destination port range, or null if not assigned. */ public @Nullable int[] getDestinationPortRange() { return mDstPortRange; } /** * Get the direction for this policy. * * See {@link Builder#Builder(int, int)} for more information. */ public @Direction int getDirection() { return mDirection; } /** * Get the IP version for this policy. * * See {@link Builder#setIpVersion(int)} for more information. * * @return IP version, or {@link #IP_VERSION_ANY} if not assigned. */ public @IpVersion int getIpVersion() { return mIpVersion; } /** * Get the flow label for this policy. * * See {@link Builder#setFlowLabel(byte[])} for more information. * * @return flow label, or null if not assigned. */ public @Nullable byte[] getFlowLabel() { return mFlowLabel; } /** * Get the QoS characteristics for this policy. * * See {@link Builder#setQosCharacteristics(QosCharacteristics)} for more information. * * @return QoS characteristics object, or null if not assigned. */ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API) public @Nullable QosCharacteristics getQosCharacteristics() { if (!SdkLevel.isAtLeastV()) { throw new UnsupportedOperationException(); } return mQosCharacteristics; } @Override public boolean equals(@Nullable Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; QosPolicyParams that = (QosPolicyParams) o; return mPolicyId == that.mPolicyId && mDscp == that.mDscp && mUserPriority == that.mUserPriority && mSrcIp.equals(that.mSrcIp) && mDstIp.equals(that.mDstIp) && mSrcPort == that.mSrcPort && mProtocol == that.mProtocol && mDstPort == that.mDstPort && Arrays.equals(mDstPortRange, that.mDstPortRange) && mDirection == that.mDirection && mIpVersion == that.mIpVersion && mFlowLabel == that.mFlowLabel && Objects.equals(mQosCharacteristics, that.mQosCharacteristics); } @Override public int hashCode() { return Objects.hash(mPolicyId, mDscp, mUserPriority, mSrcIp, mDstIp, mSrcPort, mProtocol, Arrays.hashCode(mDstPortRange), mDirection, mIpVersion, mDstPort, Arrays.hashCode(mFlowLabel), mQosCharacteristics); } @Override public String toString() { return "{policyId=" + mPolicyId + ", " + "dscp=" + mDscp + ", " + "userPriority=" + mUserPriority + ", " + "srcIp=" + mSrcIp + ", " + "dstIp=" + mDstIp + ", " + "srcPort=" + mSrcPort + ", " + "protocol=" + mProtocol + ", " + "dstPort=" + mDstPort + ", " + "dstPortRange=" + Arrays.toString(mDstPortRange) + ", " + "direction=" + mDirection + ", " + "ipVersion=" + mIpVersion + ", " + "flowLabel=" + Arrays.toString(mFlowLabel) + ", " + "qosCharacteristics=" + mQosCharacteristics + "}"; } /** @hide */ @Override public int describeContents() { return 0; } private InetAddress getInetAddrOrNull(byte[] byteAddr) { if (byteAddr == null) return null; try { return InetAddress.getByAddress(byteAddr); } catch (Exception e) { return null; } } /** @hide */ @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(mPolicyId); dest.writeInt(mDscp); dest.writeInt(mUserPriority); dest.writeByteArray(mSrcIp != null ? mSrcIp.getAddress() : null); dest.writeByteArray(mDstIp != null ? mDstIp.getAddress() : null); dest.writeInt(mSrcPort); dest.writeInt(mProtocol); dest.writeInt(mDstPort); dest.writeIntArray(mDstPortRange); dest.writeInt(mDirection); dest.writeInt(mIpVersion); dest.writeByteArray(mFlowLabel); if (SdkLevel.isAtLeastV()) { dest.writeParcelable(mQosCharacteristics, 0); } } /** @hide */ QosPolicyParams(@NonNull Parcel in) { this.mPolicyId = in.readInt(); this.mDscp = in.readInt(); this.mUserPriority = in.readInt(); this.mSrcIp = getInetAddrOrNull(in.createByteArray()); this.mDstIp = getInetAddrOrNull(in.createByteArray()); this.mSrcPort = in.readInt(); this.mProtocol = in.readInt(); this.mDstPort = in.readInt(); this.mDstPortRange = in.createIntArray(); this.mDirection = in.readInt(); this.mIpVersion = in.readInt(); this.mFlowLabel = in.createByteArray(); if (SdkLevel.isAtLeastV()) { this.mQosCharacteristics = in.readParcelable( QosCharacteristics.class.getClassLoader(), QosCharacteristics.class); } else { this.mQosCharacteristics = null; } } public static final @NonNull Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public QosPolicyParams createFromParcel(Parcel in) { return new QosPolicyParams(in); } @Override public QosPolicyParams[] newArray(int size) { return new QosPolicyParams[size]; } }; /** * Builder for {@link QosPolicyParams}. */ public static final class Builder { private final int mPolicyId; private final @Direction int mDirection; private @Nullable InetAddress mSrcIp; private @Nullable InetAddress mDstIp; private int mDscp = DSCP_ANY; private @UserPriority int mUserPriority = USER_PRIORITY_ANY; private int mSrcPort = DscpPolicy.SOURCE_PORT_ANY; private int mProtocol = PROTOCOL_ANY; private int mDstPort = DESTINATION_PORT_ANY; private @Nullable int[] mDstPortRange; private @IpVersion int mIpVersion = IP_VERSION_ANY; private byte[] mFlowLabel; private @Nullable QosCharacteristics mQosCharacteristics; /** * Constructor for {@link Builder}. * * @param policyId Unique ID to identify this policy. Each requesting application is * responsible for maintaining policy IDs unique for that app. IDs must be * in the range 1 <= policyId <= 255. * * In the case where a policy with an existing ID is created, the new policy * will be rejected. To update an existing policy, remove the existing one * before sending the new one. * @param direction Whether this policy applies to the uplink or downlink direction. */ public Builder(@IntRange(from = 1, to = 255) int policyId, @Direction int direction) { mPolicyId = policyId; mDirection = direction; } /** * Specifies that this policy matches packets with the provided source IP address. */ public @NonNull Builder setSourceAddress(@Nullable InetAddress value) { mSrcIp = value; return this; } /** * Specifies that this policy matches packets with the provided destination IP address. */ public @NonNull Builder setDestinationAddress(@Nullable InetAddress value) { mDstIp = value; return this; } /** * Specifies the DSCP value. For uplink requests, this value will be applied to packets * that match the classifier. For downlink requests, this will be part of the classifier. */ public @NonNull Builder setDscp(@IntRange(from = DSCP_ANY, to = 63) int value) { mDscp = value; return this; } /** * Specifies that the provided User Priority should be applied to packets that * match this classifier. Only applicable to downlink requests. */ public @NonNull Builder setUserPriority(@UserPriority int value) { mUserPriority = value; return this; } /** * Specifies that this policy matches packets with the provided source port. */ public @NonNull Builder setSourcePort( @IntRange(from = DscpPolicy.SOURCE_PORT_ANY, to = 65535) int value) { mSrcPort = value; return this; } /** * Specifies that this policy matches packets with the provided protocol. */ public @NonNull Builder setProtocol(@Protocol int value) { mProtocol = value; return this; } /** * Specifies that this policy matches packets with the provided destination port. * Only applicable to downlink requests. */ public @NonNull Builder setDestinationPort( @IntRange(from = DESTINATION_PORT_ANY, to = 65535) int value) { mDstPort = value; return this; } /** * Specifies that this policy matches packets with the provided destination port range. * Only applicable to uplink requests. */ public @NonNull Builder setDestinationPortRange( @IntRange(from = 0, to = 65535) int start, @IntRange(from = 0, to = 65535) int end) { mDstPortRange = new int[]{start, end}; return this; } /** * Specifies that this policy matches packets with the provided IP version. * This argument is mandatory for downlink requests, and is ignored for uplink requests. */ public @NonNull Builder setIpVersion(@IpVersion int value) { mIpVersion = value; return this; } /** * Specifies that this policy matches packets with the provided flow label. * Only applicable to downlink requests using IPv6. */ public @NonNull Builder setFlowLabel(@Nullable byte[] value) { mFlowLabel = value; return this; } /** * Specifies traffic flow parameters to use for this policy request. * This argument is mandatory for uplink requests, but optional for downlink requests. */ @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API) @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) public @NonNull Builder setQosCharacteristics( @Nullable QosCharacteristics qosCharacteristics) { if (!SdkLevel.isAtLeastV()) { throw new UnsupportedOperationException(); } mQosCharacteristics = qosCharacteristics; return this; } /** * Construct a QosPolicyParams object with the specified parameters. */ public @NonNull QosPolicyParams build() { QosPolicyParams params = new QosPolicyParams(mPolicyId, mDscp, mUserPriority, mSrcIp, mDstIp, mSrcPort, mProtocol, mDstPortRange, mDirection, mIpVersion, mDstPort, mFlowLabel, mQosCharacteristics); if (!params.validate()) { throw new IllegalArgumentException("Provided parameters are invalid"); } return params; } } }