1 /* 2 * Copyright (C) 2024 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.usd; 18 19 import android.annotation.FlaggedApi; 20 import android.annotation.IntRange; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.annotation.SystemApi; 24 import android.net.wifi.WifiNetworkSpecifier; 25 import android.net.wifi.aware.TlvBufferUtils; 26 import android.net.wifi.aware.WifiAwareUtils; 27 import android.net.wifi.flags.Flags; 28 import android.os.Parcel; 29 import android.os.Parcelable; 30 31 import java.nio.charset.StandardCharsets; 32 import java.util.Arrays; 33 import java.util.List; 34 import java.util.Objects; 35 36 37 /** 38 * Defines the configuration of USD subscribe session. 39 * 40 * @hide 41 */ 42 @SystemApi 43 @FlaggedApi(Flags.FLAG_USD) 44 public final class SubscribeConfig extends Config implements Parcelable { 45 SubscribeConfig(Builder builder)46 private SubscribeConfig(Builder builder) { 47 super(builder.mServiceName, builder.mTtlSeconds, builder.mServiceProtoType, 48 builder.mTxMatchFilterTlv, builder.mRxMatchFilterTlv, builder.mServiceSpecificInfo, 49 builder.mOperatingFrequencies); 50 mSubscribeType = builder.mSubscribeType; 51 mQueryPeriodMillis = builder.mQueryPeriodMillis; 52 mRecommendedFrequencies = builder.mRecommendedFrequencies; 53 } 54 55 @SubscribeType 56 private final int mSubscribeType; 57 private final int mQueryPeriodMillis; 58 private final int[] mRecommendedFrequencies; 59 SubscribeConfig(Parcel in)60 private SubscribeConfig(Parcel in) { 61 super(in.createByteArray(), in.readInt(), in.readInt(), in.createByteArray(), 62 in.createByteArray(), in.createByteArray(), in.createIntArray()); 63 mSubscribeType = in.readInt(); 64 mQueryPeriodMillis = in.readInt(); 65 mRecommendedFrequencies = in.createIntArray(); 66 } 67 68 @Override writeToParcel(@onNull Parcel dest, int flags)69 public void writeToParcel(@NonNull Parcel dest, int flags) { 70 dest.writeByteArray(getServiceName()); 71 dest.writeInt(getTtlSeconds()); 72 dest.writeInt(getServiceProtoType()); 73 dest.writeByteArray(getTxMatchFilterTlv()); 74 dest.writeByteArray(getRxMatchFilterTlv()); 75 dest.writeByteArray(getServiceSpecificInfo()); 76 dest.writeIntArray(getOperatingFrequenciesMhz()); 77 dest.writeInt(mSubscribeType); 78 dest.writeInt(mQueryPeriodMillis); 79 dest.writeIntArray(mRecommendedFrequencies); 80 } 81 82 @Override describeContents()83 public int describeContents() { 84 return 0; 85 } 86 87 @NonNull 88 public static final Creator<SubscribeConfig> CREATOR = new Creator<SubscribeConfig>() { 89 90 @Override 91 public SubscribeConfig createFromParcel(Parcel in) { 92 return new SubscribeConfig(in); 93 } 94 95 @Override 96 public SubscribeConfig[] newArray(int size) { 97 return new SubscribeConfig[size]; 98 } 99 }; 100 101 /** 102 * Gets the type of subscribe session. See {@code SUBSCRIBE_TYPE_XXX} for different types of 103 * subscribe. 104 * 105 * @return subscribe type 106 */ 107 @SubscribeType getSubscribeType()108 public int getSubscribeType() { 109 return mSubscribeType; 110 } 111 112 /** 113 * Gets the recommended periodicity of query transmissions for the subscribe session. 114 * 115 * @return Query period in milliseconds 116 */ 117 @IntRange(from = 0) getQueryPeriodMillis()118 public int getQueryPeriodMillis() { 119 return mQueryPeriodMillis; 120 } 121 122 /** 123 * Gets the recommended frequency list to be used for subscribe operation. See 124 * {@link Builder#setRecommendedOperatingFrequenciesMhz(int[])}. 125 * 126 * @return frequency list or null if not set 127 */ 128 @Nullable getRecommendedOperatingFrequenciesMhz()129 public int[] getRecommendedOperatingFrequenciesMhz() { 130 return mRecommendedFrequencies; 131 } 132 133 @Override toString()134 public String toString() { 135 return super.toString() + " SubscribeConfig{" + "mSubscribeType=" + mSubscribeType 136 + ", mQueryPeriodMillis=" + mQueryPeriodMillis + ", mRecommendedFrequencies=" 137 + Arrays.toString(mRecommendedFrequencies) + '}'; 138 } 139 140 @Override equals(Object o)141 public boolean equals(Object o) { 142 if (this == o) return true; 143 if (!(o instanceof SubscribeConfig that)) return false; 144 if (!super.equals(o)) return false; 145 return mSubscribeType == that.mSubscribeType 146 && mQueryPeriodMillis == that.mQueryPeriodMillis 147 && Arrays.equals(mRecommendedFrequencies, that.mRecommendedFrequencies); 148 } 149 150 @Override hashCode()151 public int hashCode() { 152 int result = Objects.hash(super.hashCode(), mSubscribeType, mQueryPeriodMillis); 153 result = 31 * result + Arrays.hashCode(mRecommendedFrequencies); 154 return result; 155 } 156 157 /** 158 * {@code SubscribeConfig} builder static inner class. 159 */ 160 @FlaggedApi(Flags.FLAG_USD) 161 public static final class Builder { 162 @SubscribeType private int mSubscribeType = SUBSCRIBE_TYPE_ACTIVE; 163 private int mQueryPeriodMillis = 100; 164 private int[] mRecommendedFrequencies = null; 165 private final byte[] mServiceName; 166 private int mTtlSeconds = 3000; 167 @ServiceProtoType private int mServiceProtoType = SERVICE_PROTO_TYPE_GENERIC; 168 private byte[] mTxMatchFilterTlv = null; 169 private byte[] mRxMatchFilterTlv = null; 170 private byte[] mServiceSpecificInfo = null; 171 private int[] mOperatingFrequencies = null; 172 173 /** 174 * Builder for {@link SubscribeConfig} 175 * 176 * @param serviceName Specify the service name of the USD session. The Service Name is a 177 * UTF-8 encoded string from 1 to 178 * {@link Characteristics#getMaxServiceNameLength()} bytes in length. 179 * The only acceptable single-byte UTF-8 symbols for a Service Name are 180 * alphanumeric values (A-Z, a-z, 0-9), the hyphen ('-'), the period 181 * ('.') and the underscore ('_'). Allvalid multi-byte UTF-8 182 * characters are acceptable in a Service Name. 183 */ Builder(@onNull String serviceName)184 public Builder(@NonNull String serviceName) { 185 Objects.requireNonNull(serviceName, "serviceName must not be null"); 186 mServiceName = serviceName.getBytes(StandardCharsets.UTF_8); 187 WifiAwareUtils.validateServiceName(mServiceName); 188 } 189 190 /** 191 * Sets the time to live for the USD session and returns a reference to this Builder 192 * enabling method chaining. Default value is 3000 seconds. 193 * 194 * @param ttlSeconds Time to live in seconds. Value 0 indicating the session does not 195 * terminate on its own. 196 * @return a reference to this Builder 197 */ 198 @NonNull setTtlSeconds(@ntRangefrom = 0) int ttlSeconds)199 public Builder setTtlSeconds(@IntRange(from = 0) int ttlSeconds) { 200 if (ttlSeconds < 0) { 201 throw new IllegalArgumentException("Invalid ttlSec - must be non-negative"); 202 } 203 mTtlSeconds = ttlSeconds; 204 return this; 205 } 206 207 /** 208 * Sets the {@code serviceProtoType} and returns a reference to this Builder enabling method 209 * chaining. Supported service protocol is defined as {@code SERVICE_PROTO_TYPE_*}. Default 210 * value is {@link #SERVICE_PROTO_TYPE_GENERIC}. 211 * 212 * @param serviceProtoType the {@code serviceProtoType} to set 213 * @return a reference to this Builder 214 */ 215 @NonNull setServiceProtoType(@erviceProtoType int serviceProtoType)216 public Builder setServiceProtoType(@ServiceProtoType int serviceProtoType) { 217 if (serviceProtoType < SERVICE_PROTO_TYPE_GENERIC 218 || serviceProtoType > SERVICE_PROTO_TYPE_CSA_MATTER) { 219 throw new IllegalArgumentException("Invalid serviceProtoType - " 220 + serviceProtoType); 221 } 222 mServiceProtoType = serviceProtoType; 223 return this; 224 } 225 226 /** 227 * Sets the {@code txMatchFilter} and returns a reference to this Builder enabling method 228 * chaining. The {@code txMatchFilter} is the ordered sequence of (length, value) pairs to 229 * be included in the subscribe frame. If not set, empty by default. 230 * 231 * <p>See Wi-Fi Aware Specification Version 4.0, section: Appendix H (Informative) Matching 232 * filter examples. 233 * 234 * @param txMatchFilter the {@code txMatchFilter} to set 235 * @return a reference to this Builder 236 */ 237 @NonNull setTxMatchFilter(@onNull List<byte[]> txMatchFilter)238 public Builder setTxMatchFilter(@NonNull List<byte[]> txMatchFilter) { 239 Objects.requireNonNull(txMatchFilter, "txMatchFilter must not be null"); 240 mTxMatchFilterTlv = new TlvBufferUtils.TlvConstructor(0, 1).allocateAndPut( 241 txMatchFilter).getArray(); 242 if (!TlvBufferUtils.isValid(mTxMatchFilterTlv, 0, 1)) { 243 throw new IllegalArgumentException( 244 "Invalid txMatchFilter configuration - LV fields do not match up to " 245 + "length"); 246 } 247 return this; 248 } 249 250 /** 251 * Sets the {@code rxMatchFilter} and returns a reference to this Builder enabling method 252 * chaining. The {@code rxMatchFilter} is the ordered sequence of (length, value) pairs 253 * that specify further the matching conditions beyond the service name used to filter 254 * the USD discovery messages. When a subscriber receives a publish message, it matches the 255 * matching filter field in the publish message against its own matching_filter_rx. If not 256 * set, empty by default. 257 * 258 * <p>See Wi-Fi Aware Specification Version 4.0, section: Appendix H (Informative) Matching 259 * filter examples. 260 * 261 * @param rxMatchFilter the {@code rxMatchFilter} to set 262 * @return a reference to this Builder 263 */ 264 @NonNull setRxMatchFilter(@onNull List<byte[]> rxMatchFilter)265 public Builder setRxMatchFilter(@NonNull List<byte[]> rxMatchFilter) { 266 Objects.requireNonNull(rxMatchFilter, "rxMatchFilter must not be null"); 267 mRxMatchFilterTlv = new TlvBufferUtils.TlvConstructor(0, 1).allocateAndPut( 268 rxMatchFilter).getArray(); 269 if (!TlvBufferUtils.isValid(mRxMatchFilterTlv, 0, 1)) { 270 throw new IllegalArgumentException( 271 "Invalid rxMatchFilter configuration - LV fields do not match up to " 272 + "length"); 273 } 274 return this; 275 } 276 277 /** 278 * Sets the susbcribe type and returns a reference to this Builder enabling method chaining. 279 * 280 * @param subscribeType the {@code subscribeType} to set 281 * @return a reference to this Builder 282 */ 283 @NonNull setSubscribeType(@ubscribeType int subscribeType)284 public Builder setSubscribeType(@SubscribeType int subscribeType) { 285 if (subscribeType < SUBSCRIBE_TYPE_PASSIVE || subscribeType > SUBSCRIBE_TYPE_ACTIVE) { 286 throw new IllegalArgumentException("Invalid subscribeType - " + subscribeType); 287 } 288 mSubscribeType = subscribeType; 289 return this; 290 } 291 292 /** 293 * Sets the query period and returns a reference to this Builder enabling method chaining. 294 * Default value is 100 ms. 295 * 296 * @param queryPeriodMillis the {@code queryPeriodMillis} to set 297 * @return a reference to this Builder 298 */ 299 @NonNull setQueryPeriodMillis(@ntRangefrom = 0) int queryPeriodMillis)300 public Builder setQueryPeriodMillis(@IntRange(from = 0) int queryPeriodMillis) { 301 if (queryPeriodMillis < 0) { 302 throw new IllegalArgumentException( 303 "Invalid queryPeriodMillis - must be non-negative"); 304 } 305 mQueryPeriodMillis = queryPeriodMillis; 306 return this; 307 } 308 309 /** 310 * Sets the recommended frequencies to use in case the framework couldn't pick a default 311 * channel for the subscriber operation. This will be a no-op if 312 * {@link #setOperatingFrequenciesMhz(int[])} is used. 313 * 314 * <p>Here is the default subscriber channel selection preference order with {@code 315 * recommendedFreqList} 316 * <ol> 317 * <li>Channel 6 in 2.4 Ghz if there is no multichannel concurrency. 318 * <li>Station channel if the station connected on non-DFS/Indoor channel. 319 * <li>Pick a channel from {@code recommendedFreqList} if regulatory permits. 320 * <li>Pick any available channel 321 * </ol> 322 * 323 * <p>Note: If multiple channels are available for the subscriber, the channel having AP 324 * with the best RSSI will be picked. 325 * 326 * @param recommendedFrequencies the {@code recommendedFreqList} to set 327 * @return a reference to this Builder 328 * @throws IllegalArgumentException if frequencies are invalid or the number frequencies 329 * are more than the number of 20 Mhz channels in 2.4 Ghz and 5 Ghz as per regulatory. 330 */ 331 @NonNull setRecommendedOperatingFrequenciesMhz( @onNull int[] recommendedFrequencies)332 public Builder setRecommendedOperatingFrequenciesMhz( 333 @NonNull int[] recommendedFrequencies) { 334 Objects.requireNonNull(recommendedFrequencies, 335 "recommendedFrequencies must not be null"); 336 if ((recommendedFrequencies.length > MAX_NUM_OF_OPERATING_FREQUENCIES) 337 || !WifiNetworkSpecifier.validateChannelFrequencyInMhz( 338 recommendedFrequencies)) { 339 throw new IllegalArgumentException("Invalid recommendedFrequencies"); 340 } 341 this.mRecommendedFrequencies = recommendedFrequencies.clone(); 342 return this; 343 } 344 345 /** 346 * Specify service specific information for the publish session. This is a free-form byte 347 * array available to the application to send additional information as part of the 348 * discovery operation - it will not be used to determine whether a publish/subscribe 349 * match occurs. Default value is null; 350 * 351 * Note: Maximum length is limited by 352 * {@link Characteristics#getMaxServiceSpecificInfoLength()} 353 * 354 * @param serviceSpecificInfo A byte-array for the service-specific 355 * information field. 356 * @return a reference to this Builder 357 */ 358 @NonNull setServiceSpecificInfo(@onNull byte[] serviceSpecificInfo)359 public Builder setServiceSpecificInfo(@NonNull byte[] serviceSpecificInfo) { 360 Objects.requireNonNull(serviceSpecificInfo, "serviceSpecificInfo must not be null"); 361 mServiceSpecificInfo = serviceSpecificInfo.clone(); 362 return this; 363 } 364 365 /** 366 * Sets the frequencies used for subscribe operation. The subscriber picks one of the 367 * frequencies from this list. This overrides the default channel selection as described 368 * below. 369 * 370 * <p>If null, here is the default subscriber channel selection preference order, 371 * <ol> 372 * <li>Channel 6 in 2.4 Ghz if there is no multichannel concurrency. 373 * <li>Station channel if the station connected on non-DFS/Indoor channel. 374 * <li>Pick a channel from {@link #setRecommendedOperatingFrequenciesMhz(int[])} if 375 * regulatory permits. 376 * <li>Pick any available channel. 377 * </ol> 378 * <p>Note: the dwell time for subscriber operation is calculated internally based on 379 * existing concurrency operation (e.g. Station + USD). 380 * 381 * @param operatingFrequencies frequencies used for subscribe operation 382 * @return a reference to this Builder 383 * @throws IllegalArgumentException if frequencies are invalid or the number frequencies 384 * are more than the number of 20 Mhz channels in 2.4 Ghz and 5 Ghz as per regulatory. 385 */ 386 @NonNull setOperatingFrequenciesMhz(@onNull int[] operatingFrequencies)387 public Builder setOperatingFrequenciesMhz(@NonNull int[] operatingFrequencies) { 388 Objects.requireNonNull(operatingFrequencies, "operatingFrequencies must not be null"); 389 if ((operatingFrequencies.length > MAX_NUM_OF_OPERATING_FREQUENCIES) 390 || !WifiNetworkSpecifier.validateChannelFrequencyInMhz(operatingFrequencies)) { 391 throw new IllegalArgumentException("Invalid operatingFrequencies"); 392 } 393 mOperatingFrequencies = operatingFrequencies.clone(); 394 return this; 395 } 396 397 /** 398 * Returns a {@code SubscribeConfig} built from the parameters previously set. 399 * 400 * @return a {@code SubscribeConfig} built with parameters of this {@code SubscribeConfig 401 * .Builder} 402 */ 403 @NonNull build()404 public SubscribeConfig build() { 405 return new SubscribeConfig(this); 406 } 407 } 408 } 409