1 /* 2 * Copyright (C) 2016 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.aware; 18 19 import android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.net.wifi.util.HexEncoding; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 26 import java.lang.annotation.Retention; 27 import java.lang.annotation.RetentionPolicy; 28 import java.nio.charset.StandardCharsets; 29 import java.util.Arrays; 30 import java.util.List; 31 import java.util.Objects; 32 33 /** 34 * Defines the configuration of a Aware publish session. Built using 35 * {@link PublishConfig.Builder}. A publish session is created using 36 * {@link WifiAwareSession#publish(PublishConfig, DiscoverySessionCallback, 37 * android.os.Handler)} or updated using 38 * {@link PublishDiscoverySession#updatePublish(PublishConfig)}. 39 */ 40 public final class PublishConfig implements Parcelable { 41 /** @hide */ 42 @IntDef({ 43 PUBLISH_TYPE_UNSOLICITED, PUBLISH_TYPE_SOLICITED }) 44 @Retention(RetentionPolicy.SOURCE) 45 public @interface PublishTypes { 46 } 47 48 /** 49 * Defines an unsolicited publish session - a publish session where the publisher is 50 * advertising itself by broadcasting on-the-air. An unsolicited publish session is paired 51 * with an passive subscribe session {@link SubscribeConfig#SUBSCRIBE_TYPE_PASSIVE}. 52 * Configuration is done using {@link PublishConfig.Builder#setPublishType(int)}. 53 */ 54 public static final int PUBLISH_TYPE_UNSOLICITED = 0; 55 56 /** 57 * Defines a solicited publish session - a publish session which is silent, waiting for a 58 * matching active subscribe session - and responding to it in unicast. A 59 * solicited publish session is paired with an active subscribe session 60 * {@link SubscribeConfig#SUBSCRIBE_TYPE_ACTIVE}. Configuration is done using 61 * {@link PublishConfig.Builder#setPublishType(int)}. 62 */ 63 public static final int PUBLISH_TYPE_SOLICITED = 1; 64 65 /** @hide */ 66 public final byte[] mServiceName; 67 68 /** @hide */ 69 public final byte[] mServiceSpecificInfo; 70 71 /** @hide */ 72 public final byte[] mMatchFilter; 73 74 /** @hide */ 75 public final int mPublishType; 76 77 /** @hide */ 78 public final int mTtlSec; 79 80 /** @hide */ 81 public final boolean mEnableTerminateNotification; 82 83 /** @hide */ 84 public final boolean mEnableRanging; 85 86 /** @hide */ PublishConfig(byte[] serviceName, byte[] serviceSpecificInfo, byte[] matchFilter, int publishType, int ttlSec, boolean enableTerminateNotification, boolean enableRanging)87 public PublishConfig(byte[] serviceName, byte[] serviceSpecificInfo, byte[] matchFilter, 88 int publishType, int ttlSec, boolean enableTerminateNotification, 89 boolean enableRanging) { 90 mServiceName = serviceName; 91 mServiceSpecificInfo = serviceSpecificInfo; 92 mMatchFilter = matchFilter; 93 mPublishType = publishType; 94 mTtlSec = ttlSec; 95 mEnableTerminateNotification = enableTerminateNotification; 96 mEnableRanging = enableRanging; 97 } 98 99 @Override toString()100 public String toString() { 101 return "PublishConfig [mServiceName='" + (mServiceName == null ? "<null>" : String.valueOf( 102 HexEncoding.encode(mServiceName))) + ", mServiceName.length=" + ( 103 mServiceName == null ? 0 : mServiceName.length) + ", mServiceSpecificInfo='" + ( 104 (mServiceSpecificInfo == null) ? "<null>" : String.valueOf( 105 HexEncoding.encode(mServiceSpecificInfo))) 106 + ", mServiceSpecificInfo.length=" + (mServiceSpecificInfo == null ? 0 107 : mServiceSpecificInfo.length) + ", mMatchFilter=" 108 + (new TlvBufferUtils.TlvIterable(0, 1, mMatchFilter)).toString() 109 + ", mMatchFilter.length=" + (mMatchFilter == null ? 0 : mMatchFilter.length) 110 + ", mPublishType=" + mPublishType + ", mTtlSec=" + mTtlSec 111 + ", mEnableTerminateNotification=" + mEnableTerminateNotification 112 + ", mEnableRanging=" + mEnableRanging + "]"; 113 } 114 115 @Override describeContents()116 public int describeContents() { 117 return 0; 118 } 119 120 @Override writeToParcel(Parcel dest, int flags)121 public void writeToParcel(Parcel dest, int flags) { 122 dest.writeByteArray(mServiceName); 123 dest.writeByteArray(mServiceSpecificInfo); 124 dest.writeByteArray(mMatchFilter); 125 dest.writeInt(mPublishType); 126 dest.writeInt(mTtlSec); 127 dest.writeInt(mEnableTerminateNotification ? 1 : 0); 128 dest.writeInt(mEnableRanging ? 1 : 0); 129 } 130 131 public static final @android.annotation.NonNull Creator<PublishConfig> CREATOR = new Creator<PublishConfig>() { 132 @Override 133 public PublishConfig[] newArray(int size) { 134 return new PublishConfig[size]; 135 } 136 137 @Override 138 public PublishConfig createFromParcel(Parcel in) { 139 byte[] serviceName = in.createByteArray(); 140 byte[] ssi = in.createByteArray(); 141 byte[] matchFilter = in.createByteArray(); 142 int publishType = in.readInt(); 143 int ttlSec = in.readInt(); 144 boolean enableTerminateNotification = in.readInt() != 0; 145 boolean enableRanging = in.readInt() != 0; 146 147 return new PublishConfig(serviceName, ssi, matchFilter, publishType, 148 ttlSec, enableTerminateNotification, enableRanging); 149 } 150 }; 151 152 @Override equals(Object o)153 public boolean equals(Object o) { 154 if (this == o) { 155 return true; 156 } 157 158 if (!(o instanceof PublishConfig)) { 159 return false; 160 } 161 162 PublishConfig lhs = (PublishConfig) o; 163 164 return Arrays.equals(mServiceName, lhs.mServiceName) && Arrays.equals(mServiceSpecificInfo, 165 lhs.mServiceSpecificInfo) && Arrays.equals(mMatchFilter, lhs.mMatchFilter) 166 && mPublishType == lhs.mPublishType 167 && mTtlSec == lhs.mTtlSec 168 && mEnableTerminateNotification == lhs.mEnableTerminateNotification 169 && mEnableRanging == lhs.mEnableRanging; 170 } 171 172 @Override hashCode()173 public int hashCode() { 174 return Objects.hash(Arrays.hashCode(mServiceName), Arrays.hashCode(mServiceSpecificInfo), 175 Arrays.hashCode(mMatchFilter), mPublishType, mTtlSec, mEnableTerminateNotification, 176 mEnableRanging); 177 } 178 179 /** 180 * Verifies that the contents of the PublishConfig are valid. Otherwise 181 * throws an IllegalArgumentException. 182 * 183 * @hide 184 */ assertValid(Characteristics characteristics, boolean rttSupported)185 public void assertValid(Characteristics characteristics, boolean rttSupported) 186 throws IllegalArgumentException { 187 WifiAwareUtils.validateServiceName(mServiceName); 188 189 if (!TlvBufferUtils.isValid(mMatchFilter, 0, 1)) { 190 throw new IllegalArgumentException( 191 "Invalid txFilter configuration - LV fields do not match up to length"); 192 } 193 if (mPublishType < PUBLISH_TYPE_UNSOLICITED || mPublishType > PUBLISH_TYPE_SOLICITED) { 194 throw new IllegalArgumentException("Invalid publishType - " + mPublishType); 195 } 196 if (mTtlSec < 0) { 197 throw new IllegalArgumentException("Invalid ttlSec - must be non-negative"); 198 } 199 200 if (characteristics != null) { 201 int maxServiceNameLength = characteristics.getMaxServiceNameLength(); 202 if (maxServiceNameLength != 0 && mServiceName.length > maxServiceNameLength) { 203 throw new IllegalArgumentException( 204 "Service name longer than supported by device characteristics"); 205 } 206 int maxServiceSpecificInfoLength = characteristics.getMaxServiceSpecificInfoLength(); 207 if (maxServiceSpecificInfoLength != 0 && mServiceSpecificInfo != null 208 && mServiceSpecificInfo.length > maxServiceSpecificInfoLength) { 209 throw new IllegalArgumentException( 210 "Service specific info longer than supported by device characteristics"); 211 } 212 int maxMatchFilterLength = characteristics.getMaxMatchFilterLength(); 213 if (maxMatchFilterLength != 0 && mMatchFilter != null 214 && mMatchFilter.length > maxMatchFilterLength) { 215 throw new IllegalArgumentException( 216 "Match filter longer than supported by device characteristics"); 217 } 218 } 219 220 if (!rttSupported && mEnableRanging) { 221 throw new IllegalArgumentException("Ranging is not supported"); 222 } 223 } 224 225 /** 226 * Builder used to build {@link PublishConfig} objects. 227 */ 228 public static final class Builder { 229 private byte[] mServiceName; 230 private byte[] mServiceSpecificInfo; 231 private byte[] mMatchFilter; 232 private int mPublishType = PUBLISH_TYPE_UNSOLICITED; 233 private int mTtlSec = 0; 234 private boolean mEnableTerminateNotification = true; 235 private boolean mEnableRanging = false; 236 237 /** 238 * Specify the service name of the publish session. The actual on-air 239 * value is a 6 byte hashed representation of this string. 240 * <p> 241 * The Service Name is a UTF-8 encoded string from 1 to 255 bytes in length. 242 * The only acceptable single-byte UTF-8 symbols for a Service Name are alphanumeric 243 * values (A-Z, a-z, 0-9), the hyphen ('-'), the period ('.') and the underscore ('_'). All 244 * valid multi-byte UTF-8 characters are acceptable in a Service Name. 245 * <p> 246 * Note: for compatibility with devices running Android 11 or older, avoid using 247 * underscore ('_') symbol as a single-byte UTF-8 service name. 248 * <p> 249 * Must be called - an empty ServiceName is not valid. 250 * 251 * @param serviceName The service name for the publish session. 252 * 253 * @return The builder to facilitate chaining 254 * {@code builder.setXXX(..).setXXX(..)}. 255 */ setServiceName(@onNull String serviceName)256 public Builder setServiceName(@NonNull String serviceName) { 257 if (serviceName == null) { 258 throw new IllegalArgumentException("Invalid service name - must be non-null"); 259 } 260 mServiceName = serviceName.getBytes(StandardCharsets.UTF_8); 261 return this; 262 } 263 264 /** 265 * Specify service specific information for the publish session. This is 266 * a free-form byte array available to the application to send 267 * additional information as part of the discovery operation - it 268 * will not be used to determine whether a publish/subscribe match 269 * occurs. 270 * <p> 271 * Optional. Empty by default. 272 * 273 * @param serviceSpecificInfo A byte-array for the service-specific 274 * information field. 275 * 276 * @return The builder to facilitate chaining 277 * {@code builder.setXXX(..).setXXX(..)}. 278 */ setServiceSpecificInfo(@ullable byte[] serviceSpecificInfo)279 public Builder setServiceSpecificInfo(@Nullable byte[] serviceSpecificInfo) { 280 mServiceSpecificInfo = serviceSpecificInfo; 281 return this; 282 } 283 284 /** 285 * The match filter for a publish session. Used to determine whether a service 286 * discovery occurred - in addition to relying on the service name. 287 * <p> 288 * Optional. Empty by default. 289 * 290 * @param matchFilter A list of match filter entries (each of which is an arbitrary byte 291 * array). 292 * 293 * @return The builder to facilitate chaining 294 * {@code builder.setXXX(..).setXXX(..)}. 295 */ setMatchFilter(@ullable List<byte[]> matchFilter)296 public Builder setMatchFilter(@Nullable List<byte[]> matchFilter) { 297 mMatchFilter = new TlvBufferUtils.TlvConstructor(0, 1).allocateAndPut( 298 matchFilter).getArray(); 299 return this; 300 } 301 302 /** 303 * Specify the type of the publish session: solicited (aka active - publish 304 * packets are transmitted over-the-air), or unsolicited (aka passive - 305 * no publish packets are transmitted, a match is made against an active 306 * subscribe session whose packets are transmitted over-the-air). 307 * 308 * @param publishType Publish session type: 309 * {@link PublishConfig#PUBLISH_TYPE_SOLICITED} or 310 * {@link PublishConfig#PUBLISH_TYPE_UNSOLICITED} (the default). 311 * 312 * @return The builder to facilitate chaining 313 * {@code builder.setXXX(..).setXXX(..)}. 314 */ setPublishType(@ublishTypes int publishType)315 public Builder setPublishType(@PublishTypes int publishType) { 316 if (publishType < PUBLISH_TYPE_UNSOLICITED || publishType > PUBLISH_TYPE_SOLICITED) { 317 throw new IllegalArgumentException("Invalid publishType - " + publishType); 318 } 319 mPublishType = publishType; 320 return this; 321 } 322 323 /** 324 * Sets the time interval (in seconds) an unsolicited ( 325 * {@link PublishConfig.Builder#setPublishType(int)}) publish session 326 * will be alive - broadcasting a packet. When the TTL is reached 327 * an event will be generated for 328 * {@link DiscoverySessionCallback#onSessionTerminated()} [unless 329 * {@link #setTerminateNotificationEnabled(boolean)} disables the callback]. 330 * <p> 331 * Optional. 0 by default - indicating the session doesn't terminate on its own. 332 * Session will be terminated when {@link DiscoverySession#close()} is 333 * called. 334 * 335 * @param ttlSec Lifetime of a publish session in seconds. 336 * 337 * @return The builder to facilitate chaining 338 * {@code builder.setXXX(..).setXXX(..)}. 339 */ setTtlSec(int ttlSec)340 public Builder setTtlSec(int ttlSec) { 341 if (ttlSec < 0) { 342 throw new IllegalArgumentException("Invalid ttlSec - must be non-negative"); 343 } 344 mTtlSec = ttlSec; 345 return this; 346 } 347 348 /** 349 * Configure whether a publish terminate notification 350 * {@link DiscoverySessionCallback#onSessionTerminated()} is reported 351 * back to the callback. 352 * 353 * @param enable If true the terminate callback will be called when the 354 * publish is terminated. Otherwise it will not be called. 355 * 356 * @return The builder to facilitate chaining 357 * {@code builder.setXXX(..).setXXX(..)}. 358 */ setTerminateNotificationEnabled(boolean enable)359 public Builder setTerminateNotificationEnabled(boolean enable) { 360 mEnableTerminateNotification = enable; 361 return this; 362 } 363 364 /** 365 * Configure whether the publish discovery session supports ranging and allows peers to 366 * measure distance to it. This API is used in conjunction with 367 * {@link SubscribeConfig.Builder#setMinDistanceMm(int)} and 368 * {@link SubscribeConfig.Builder#setMaxDistanceMm(int)} to specify a minimum and/or 369 * maximum distance at which discovery will be triggered. 370 * <p> 371 * Optional. Disabled by default - i.e. any peer attempt to measure distance to this device 372 * will be refused and discovery will proceed without ranging constraints. 373 * <p> 374 * The device must support Wi-Fi RTT for this feature to be used. Feature support is checked 375 * as described in {@link android.net.wifi.rtt}. 376 * 377 * @param enable If true, ranging is supported on request of the peer. 378 * 379 * @return The builder to facilitate chaining 380 * {@code builder.setXXX(..).setXXX(..)}. 381 */ setRangingEnabled(boolean enable)382 public Builder setRangingEnabled(boolean enable) { 383 mEnableRanging = enable; 384 return this; 385 } 386 387 /** 388 * Build {@link PublishConfig} given the current requests made on the 389 * builder. 390 */ build()391 public PublishConfig build() { 392 return new PublishConfig(mServiceName, mServiceSpecificInfo, mMatchFilter, mPublishType, 393 mTtlSec, mEnableTerminateNotification, mEnableRanging); 394 } 395 } 396 } 397