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