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