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