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 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 */ PublishConfig(byte[] serviceName, byte[] serviceSpecificInfo, byte[] matchFilter, int publishType, int ttlSec, boolean enableTerminateNotification)84 public PublishConfig(byte[] serviceName, byte[] serviceSpecificInfo, byte[] matchFilter, 85 int publishType, int ttlSec, boolean enableTerminateNotification) { 86 mServiceName = serviceName; 87 mServiceSpecificInfo = serviceSpecificInfo; 88 mMatchFilter = matchFilter; 89 mPublishType = publishType; 90 mTtlSec = ttlSec; 91 mEnableTerminateNotification = enableTerminateNotification; 92 } 93 94 @Override toString()95 public String toString() { 96 return "PublishConfig [mServiceName='" + mServiceName + ", mServiceSpecificInfo='" + ( 97 (mServiceSpecificInfo == null) ? "null" : HexEncoding.encode(mServiceSpecificInfo)) 98 + ", mMatchFilter=" + (new TlvBufferUtils.TlvIterable(0, 1, 99 mMatchFilter)).toString() + ", mPublishType=" + mPublishType 100 + ", mTtlSec=" + mTtlSec + ", mEnableTerminateNotification=" 101 + mEnableTerminateNotification + "]"; 102 } 103 104 @Override describeContents()105 public int describeContents() { 106 return 0; 107 } 108 109 @Override writeToParcel(Parcel dest, int flags)110 public void writeToParcel(Parcel dest, int flags) { 111 dest.writeByteArray(mServiceName); 112 dest.writeByteArray(mServiceSpecificInfo); 113 dest.writeByteArray(mMatchFilter); 114 dest.writeInt(mPublishType); 115 dest.writeInt(mTtlSec); 116 dest.writeInt(mEnableTerminateNotification ? 1 : 0); 117 } 118 119 public static final Creator<PublishConfig> CREATOR = new Creator<PublishConfig>() { 120 @Override 121 public PublishConfig[] newArray(int size) { 122 return new PublishConfig[size]; 123 } 124 125 @Override 126 public PublishConfig createFromParcel(Parcel in) { 127 byte[] serviceName = in.createByteArray(); 128 byte[] ssi = in.createByteArray(); 129 byte[] matchFilter = in.createByteArray(); 130 int publishType = in.readInt(); 131 int ttlSec = in.readInt(); 132 boolean enableTerminateNotification = in.readInt() != 0; 133 134 return new PublishConfig(serviceName, ssi, matchFilter, publishType, 135 ttlSec, enableTerminateNotification); 136 } 137 }; 138 139 @Override equals(Object o)140 public boolean equals(Object o) { 141 if (this == o) { 142 return true; 143 } 144 145 if (!(o instanceof PublishConfig)) { 146 return false; 147 } 148 149 PublishConfig lhs = (PublishConfig) o; 150 151 return Arrays.equals(mServiceName, lhs.mServiceName) && Arrays.equals(mServiceSpecificInfo, 152 lhs.mServiceSpecificInfo) && Arrays.equals(mMatchFilter, lhs.mMatchFilter) 153 && mPublishType == lhs.mPublishType 154 && mTtlSec == lhs.mTtlSec 155 && mEnableTerminateNotification == lhs.mEnableTerminateNotification; 156 } 157 158 @Override hashCode()159 public int hashCode() { 160 int result = 17; 161 162 result = 31 * result + Arrays.hashCode(mServiceName); 163 result = 31 * result + Arrays.hashCode(mServiceSpecificInfo); 164 result = 31 * result + Arrays.hashCode(mMatchFilter); 165 result = 31 * result + mPublishType; 166 result = 31 * result + mTtlSec; 167 result = 31 * result + (mEnableTerminateNotification ? 1 : 0); 168 169 return result; 170 } 171 172 /** 173 * Verifies that the contents of the PublishConfig are valid. Otherwise 174 * throws an IllegalArgumentException. 175 * 176 * @hide 177 */ assertValid(Characteristics characteristics)178 public void assertValid(Characteristics characteristics) 179 throws IllegalArgumentException { 180 WifiAwareUtils.validateServiceName(mServiceName); 181 182 if (!TlvBufferUtils.isValid(mMatchFilter, 0, 1)) { 183 throw new IllegalArgumentException( 184 "Invalid txFilter configuration - LV fields do not match up to length"); 185 } 186 if (mPublishType < PUBLISH_TYPE_UNSOLICITED || mPublishType > PUBLISH_TYPE_SOLICITED) { 187 throw new IllegalArgumentException("Invalid publishType - " + mPublishType); 188 } 189 if (mTtlSec < 0) { 190 throw new IllegalArgumentException("Invalid ttlSec - must be non-negative"); 191 } 192 193 if (characteristics != null) { 194 int maxServiceNameLength = characteristics.getMaxServiceNameLength(); 195 if (maxServiceNameLength != 0 && mServiceName.length > maxServiceNameLength) { 196 throw new IllegalArgumentException( 197 "Service name longer than supported by device characteristics"); 198 } 199 int maxServiceSpecificInfoLength = characteristics.getMaxServiceSpecificInfoLength(); 200 if (maxServiceSpecificInfoLength != 0 && mServiceSpecificInfo != null 201 && mServiceSpecificInfo.length > maxServiceSpecificInfoLength) { 202 throw new IllegalArgumentException( 203 "Service specific info longer than supported by device characteristics"); 204 } 205 int maxMatchFilterLength = characteristics.getMaxMatchFilterLength(); 206 if (maxMatchFilterLength != 0 && mMatchFilter != null 207 && mMatchFilter.length > maxMatchFilterLength) { 208 throw new IllegalArgumentException( 209 "Match filter longer than supported by device characteristics"); 210 } 211 } 212 } 213 214 /** 215 * Builder used to build {@link PublishConfig} objects. 216 */ 217 public static final class Builder { 218 private byte[] mServiceName; 219 private byte[] mServiceSpecificInfo; 220 private byte[] mMatchFilter; 221 private int mPublishType = PUBLISH_TYPE_UNSOLICITED; 222 private int mTtlSec = 0; 223 private boolean mEnableTerminateNotification = true; 224 225 /** 226 * Specify the service name of the publish session. The actual on-air 227 * value is a 6 byte hashed representation of this string. 228 * <p> 229 * The Service Name is a UTF-8 encoded string from 1 to 255 bytes in length. 230 * The only acceptable single-byte UTF-8 symbols for a Service Name are alphanumeric 231 * values (A-Z, a-z, 0-9), the hyphen ('-'), and the period ('.'). All valid multi-byte 232 * UTF-8 characters are acceptable in a Service Name. 233 * <p> 234 * Must be called - an empty ServiceName is not valid. 235 * 236 * @param serviceName The service name for the publish session. 237 * 238 * @return The builder to facilitate chaining 239 * {@code builder.setXXX(..).setXXX(..)}. 240 */ setServiceName(@onNull String serviceName)241 public Builder setServiceName(@NonNull String serviceName) { 242 if (serviceName == null) { 243 throw new IllegalArgumentException("Invalid service name - must be non-null"); 244 } 245 mServiceName = serviceName.getBytes(StandardCharsets.UTF_8); 246 return this; 247 } 248 249 /** 250 * Specify service specific information for the publish session. This is 251 * a free-form byte array available to the application to send 252 * additional information as part of the discovery operation - it 253 * will not be used to determine whether a publish/subscribe match 254 * occurs. 255 * <p> 256 * Optional. Empty by default. 257 * 258 * @param serviceSpecificInfo A byte-array for the service-specific 259 * information field. 260 * 261 * @return The builder to facilitate chaining 262 * {@code builder.setXXX(..).setXXX(..)}. 263 */ setServiceSpecificInfo(@ullable byte[] serviceSpecificInfo)264 public Builder setServiceSpecificInfo(@Nullable byte[] serviceSpecificInfo) { 265 mServiceSpecificInfo = serviceSpecificInfo; 266 return this; 267 } 268 269 /** 270 * The match filter for a publish session. Used to determine whether a service 271 * discovery occurred - in addition to relying on the service name. 272 * <p> 273 * Optional. Empty by default. 274 * 275 * @param matchFilter A list of match filter entries (each of which is an arbitrary byte 276 * array). 277 * 278 * @return The builder to facilitate chaining 279 * {@code builder.setXXX(..).setXXX(..)}. 280 */ setMatchFilter(@ullable List<byte[]> matchFilter)281 public Builder setMatchFilter(@Nullable List<byte[]> matchFilter) { 282 mMatchFilter = new TlvBufferUtils.TlvConstructor(0, 1).allocateAndPut( 283 matchFilter).getArray(); 284 return this; 285 } 286 287 /** 288 * Specify the type of the publish session: solicited (aka active - publish 289 * packets are transmitted over-the-air), or unsolicited (aka passive - 290 * no publish packets are transmitted, a match is made against an active 291 * subscribe session whose packets are transmitted over-the-air). 292 * 293 * @param publishType Publish session type: 294 * {@link PublishConfig#PUBLISH_TYPE_SOLICITED} or 295 * {@link PublishConfig#PUBLISH_TYPE_UNSOLICITED} (the default). 296 * 297 * @return The builder to facilitate chaining 298 * {@code builder.setXXX(..).setXXX(..)}. 299 */ setPublishType(@ublishTypes int publishType)300 public Builder setPublishType(@PublishTypes int publishType) { 301 if (publishType < PUBLISH_TYPE_UNSOLICITED || publishType > PUBLISH_TYPE_SOLICITED) { 302 throw new IllegalArgumentException("Invalid publishType - " + publishType); 303 } 304 mPublishType = publishType; 305 return this; 306 } 307 308 /** 309 * Sets the time interval (in seconds) an unsolicited ( 310 * {@link PublishConfig.Builder#setPublishType(int)}) publish session 311 * will be alive - broadcasting a packet. When the TTL is reached 312 * an event will be generated for 313 * {@link DiscoverySessionCallback#onSessionTerminated()} [unless 314 * {@link #setTerminateNotificationEnabled(boolean)} disables the callback]. 315 * <p> 316 * Optional. 0 by default - indicating the session doesn't terminate on its own. 317 * Session will be terminated when {@link DiscoverySession#close()} is 318 * called. 319 * 320 * @param ttlSec Lifetime of a publish session in seconds. 321 * 322 * @return The builder to facilitate chaining 323 * {@code builder.setXXX(..).setXXX(..)}. 324 */ setTtlSec(int ttlSec)325 public Builder setTtlSec(int ttlSec) { 326 if (ttlSec < 0) { 327 throw new IllegalArgumentException("Invalid ttlSec - must be non-negative"); 328 } 329 mTtlSec = ttlSec; 330 return this; 331 } 332 333 /** 334 * Configure whether a publish terminate notification 335 * {@link DiscoverySessionCallback#onSessionTerminated()} is reported 336 * back to the callback. 337 * 338 * @param enable If true the terminate callback will be called when the 339 * publish is terminated. Otherwise it will not be called. 340 * 341 * @return The builder to facilitate chaining 342 * {@code builder.setXXX(..).setXXX(..)}. 343 */ setTerminateNotificationEnabled(boolean enable)344 public Builder setTerminateNotificationEnabled(boolean enable) { 345 mEnableTerminateNotification = enable; 346 return this; 347 } 348 349 /** 350 * Build {@link PublishConfig} given the current requests made on the 351 * builder. 352 */ build()353 public PublishConfig build() { 354 return new PublishConfig(mServiceName, mServiceSpecificInfo, mMatchFilter, mPublishType, 355 mTtlSec, mEnableTerminateNotification); 356 } 357 } 358 } 359