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 */ PublishConfig(byte[] serviceName, byte[] serviceSpecificInfo, byte[] matchFilter, int publishType, int ttlSec, boolean enableTerminateNotification, boolean enableRanging)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 toString()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 describeContents()117 public int describeContents() { 118 return 0; 119 } 120 121 @Override writeToParcel(Parcel dest, int flags)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 @android.annotation.NonNull 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 equals(Object o)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 hashCode()174 public int hashCode() { 175 return Objects.hash(Arrays.hashCode(mServiceName), Arrays.hashCode(mServiceSpecificInfo), 176 Arrays.hashCode(mMatchFilter), mPublishType, mTtlSec, mEnableTerminateNotification, 177 mEnableRanging); 178 } 179 180 /** 181 * Verifies that the contents of the PublishConfig are valid. Otherwise 182 * throws an IllegalArgumentException. 183 * 184 * @hide 185 */ assertValid(Characteristics characteristics, boolean rttSupported)186 public void assertValid(Characteristics characteristics, boolean rttSupported) 187 throws IllegalArgumentException { 188 WifiAwareUtils.validateServiceName(mServiceName); 189 190 if (!TlvBufferUtils.isValid(mMatchFilter, 0, 1)) { 191 throw new IllegalArgumentException( 192 "Invalid txFilter configuration - LV fields do not match up to length"); 193 } 194 if (mPublishType < PUBLISH_TYPE_UNSOLICITED || mPublishType > PUBLISH_TYPE_SOLICITED) { 195 throw new IllegalArgumentException("Invalid publishType - " + mPublishType); 196 } 197 if (mTtlSec < 0) { 198 throw new IllegalArgumentException("Invalid ttlSec - must be non-negative"); 199 } 200 201 if (characteristics != null) { 202 int maxServiceNameLength = characteristics.getMaxServiceNameLength(); 203 if (maxServiceNameLength != 0 && mServiceName.length > maxServiceNameLength) { 204 throw new IllegalArgumentException( 205 "Service name longer than supported by device characteristics"); 206 } 207 int maxServiceSpecificInfoLength = characteristics.getMaxServiceSpecificInfoLength(); 208 if (maxServiceSpecificInfoLength != 0 && mServiceSpecificInfo != null 209 && mServiceSpecificInfo.length > maxServiceSpecificInfoLength) { 210 throw new IllegalArgumentException( 211 "Service specific info longer than supported by device characteristics"); 212 } 213 int maxMatchFilterLength = characteristics.getMaxMatchFilterLength(); 214 if (maxMatchFilterLength != 0 && mMatchFilter != null 215 && mMatchFilter.length > maxMatchFilterLength) { 216 throw new IllegalArgumentException( 217 "Match filter longer than supported by device characteristics"); 218 } 219 } 220 221 if (!rttSupported && mEnableRanging) { 222 throw new IllegalArgumentException("Ranging is not supported"); 223 } 224 } 225 226 /** 227 * Builder used to build {@link PublishConfig} objects. 228 */ 229 public static final class Builder { 230 private byte[] mServiceName; 231 private byte[] mServiceSpecificInfo; 232 private byte[] mMatchFilter; 233 private int mPublishType = PUBLISH_TYPE_UNSOLICITED; 234 private int mTtlSec = 0; 235 private boolean mEnableTerminateNotification = true; 236 private boolean mEnableRanging = false; 237 238 /** 239 * Specify the service name of the publish session. The actual on-air 240 * value is a 6 byte hashed representation of this string. 241 * <p> 242 * The Service Name is a UTF-8 encoded string from 1 to 255 bytes in length. 243 * The only acceptable single-byte UTF-8 symbols for a Service Name are alphanumeric 244 * values (A-Z, a-z, 0-9), the hyphen ('-'), and the period ('.'). All valid multi-byte 245 * UTF-8 characters are acceptable in a Service Name. 246 * <p> 247 * Must be called - an empty ServiceName is not valid. 248 * 249 * @param serviceName The service name for the publish session. 250 * 251 * @return The builder to facilitate chaining 252 * {@code builder.setXXX(..).setXXX(..)}. 253 */ setServiceName(@onNull String serviceName)254 public Builder setServiceName(@NonNull String serviceName) { 255 if (serviceName == null) { 256 throw new IllegalArgumentException("Invalid service name - must be non-null"); 257 } 258 mServiceName = serviceName.getBytes(StandardCharsets.UTF_8); 259 return this; 260 } 261 262 /** 263 * Specify service specific information for the publish session. This is 264 * a free-form byte array available to the application to send 265 * additional information as part of the discovery operation - it 266 * will not be used to determine whether a publish/subscribe match 267 * occurs. 268 * <p> 269 * Optional. Empty by default. 270 * 271 * @param serviceSpecificInfo A byte-array for the service-specific 272 * information field. 273 * 274 * @return The builder to facilitate chaining 275 * {@code builder.setXXX(..).setXXX(..)}. 276 */ setServiceSpecificInfo(@ullable byte[] serviceSpecificInfo)277 public Builder setServiceSpecificInfo(@Nullable byte[] serviceSpecificInfo) { 278 mServiceSpecificInfo = serviceSpecificInfo; 279 return this; 280 } 281 282 /** 283 * The match filter for a publish session. Used to determine whether a service 284 * discovery occurred - in addition to relying on the service name. 285 * <p> 286 * Optional. Empty by default. 287 * 288 * @param matchFilter A list of match filter entries (each of which is an arbitrary byte 289 * array). 290 * 291 * @return The builder to facilitate chaining 292 * {@code builder.setXXX(..).setXXX(..)}. 293 */ setMatchFilter(@ullable List<byte[]> matchFilter)294 public Builder setMatchFilter(@Nullable List<byte[]> matchFilter) { 295 mMatchFilter = new TlvBufferUtils.TlvConstructor(0, 1).allocateAndPut( 296 matchFilter).getArray(); 297 return this; 298 } 299 300 /** 301 * Specify the type of the publish session: solicited (aka active - publish 302 * packets are transmitted over-the-air), or unsolicited (aka passive - 303 * no publish packets are transmitted, a match is made against an active 304 * subscribe session whose packets are transmitted over-the-air). 305 * 306 * @param publishType Publish session type: 307 * {@link PublishConfig#PUBLISH_TYPE_SOLICITED} or 308 * {@link PublishConfig#PUBLISH_TYPE_UNSOLICITED} (the default). 309 * 310 * @return The builder to facilitate chaining 311 * {@code builder.setXXX(..).setXXX(..)}. 312 */ setPublishType(@ublishTypes int publishType)313 public Builder setPublishType(@PublishTypes int publishType) { 314 if (publishType < PUBLISH_TYPE_UNSOLICITED || publishType > PUBLISH_TYPE_SOLICITED) { 315 throw new IllegalArgumentException("Invalid publishType - " + publishType); 316 } 317 mPublishType = publishType; 318 return this; 319 } 320 321 /** 322 * Sets the time interval (in seconds) an unsolicited ( 323 * {@link PublishConfig.Builder#setPublishType(int)}) publish session 324 * will be alive - broadcasting a packet. When the TTL is reached 325 * an event will be generated for 326 * {@link DiscoverySessionCallback#onSessionTerminated()} [unless 327 * {@link #setTerminateNotificationEnabled(boolean)} disables the callback]. 328 * <p> 329 * Optional. 0 by default - indicating the session doesn't terminate on its own. 330 * Session will be terminated when {@link DiscoverySession#close()} is 331 * called. 332 * 333 * @param ttlSec Lifetime of a publish session in seconds. 334 * 335 * @return The builder to facilitate chaining 336 * {@code builder.setXXX(..).setXXX(..)}. 337 */ setTtlSec(int ttlSec)338 public Builder setTtlSec(int ttlSec) { 339 if (ttlSec < 0) { 340 throw new IllegalArgumentException("Invalid ttlSec - must be non-negative"); 341 } 342 mTtlSec = ttlSec; 343 return this; 344 } 345 346 /** 347 * Configure whether a publish terminate notification 348 * {@link DiscoverySessionCallback#onSessionTerminated()} is reported 349 * back to the callback. 350 * 351 * @param enable If true the terminate callback will be called when the 352 * publish is terminated. Otherwise it will not be called. 353 * 354 * @return The builder to facilitate chaining 355 * {@code builder.setXXX(..).setXXX(..)}. 356 */ setTerminateNotificationEnabled(boolean enable)357 public Builder setTerminateNotificationEnabled(boolean enable) { 358 mEnableTerminateNotification = enable; 359 return this; 360 } 361 362 /** 363 * Configure whether the publish discovery session supports ranging and allows peers to 364 * measure distance to it. This API is used in conjunction with 365 * {@link SubscribeConfig.Builder#setMinDistanceMm(int)} and 366 * {@link SubscribeConfig.Builder#setMaxDistanceMm(int)} to specify a minimum and/or 367 * maximum distance at which discovery will be triggered. 368 * <p> 369 * Optional. Disabled by default - i.e. any peer attempt to measure distance to this device 370 * will be refused and discovery will proceed without ranging constraints. 371 * <p> 372 * The device must support Wi-Fi RTT for this feature to be used. Feature support is checked 373 * as described in {@link android.net.wifi.rtt}. 374 * 375 * @param enable If true, ranging is supported on request of the peer. 376 * 377 * @return The builder to facilitate chaining 378 * {@code builder.setXXX(..).setXXX(..)}. 379 */ setRangingEnabled(boolean enable)380 public Builder setRangingEnabled(boolean enable) { 381 mEnableRanging = enable; 382 return this; 383 } 384 385 /** 386 * Build {@link PublishConfig} given the current requests made on the 387 * builder. 388 */ build()389 public PublishConfig build() { 390 return new PublishConfig(mServiceName, mServiceSpecificInfo, mMatchFilter, mPublishType, 391 mTtlSec, mEnableTerminateNotification, mEnableRanging); 392 } 393 } 394 } 395