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 subscribe session. Built using
36  * {@link SubscribeConfig.Builder}. Subscribe is done using
37  * {@link WifiAwareSession#subscribe(SubscribeConfig, DiscoverySessionCallback,
38  * android.os.Handler)} or
39  * {@link SubscribeDiscoverySession#updateSubscribe(SubscribeConfig)}.
40  */
41 public final class SubscribeConfig implements Parcelable {
42     /** @hide */
43     @IntDef({
44             SUBSCRIBE_TYPE_PASSIVE, SUBSCRIBE_TYPE_ACTIVE })
45     @Retention(RetentionPolicy.SOURCE)
46     public @interface SubscribeTypes {
47     }
48 
49     /**
50      * Defines a passive subscribe session - a subscribe session where
51      * subscribe packets are not transmitted over-the-air and the device listens
52      * and matches to transmitted publish packets. Configuration is done using
53      * {@link SubscribeConfig.Builder#setSubscribeType(int)}.
54      */
55     public static final int SUBSCRIBE_TYPE_PASSIVE = 0;
56 
57     /**
58      * Defines an active subscribe session - a subscribe session where
59      * subscribe packets are transmitted over-the-air. Configuration is done
60      * using {@link SubscribeConfig.Builder#setSubscribeType(int)}.
61      */
62     public static final int SUBSCRIBE_TYPE_ACTIVE = 1;
63 
64     /** @hide */
65     public final byte[] mServiceName;
66 
67     /** @hide */
68     public final byte[] mServiceSpecificInfo;
69 
70     /** @hide */
71     public final byte[] mMatchFilter;
72 
73     /** @hide */
74     public final int mSubscribeType;
75 
76     /** @hide */
77     public final int mTtlSec;
78 
79     /** @hide */
80     public final boolean mEnableTerminateNotification;
81 
82     /** @hide */
83     public final boolean mMinDistanceMmSet;
84 
85     /** @hide */
86     public final int mMinDistanceMm;
87 
88     /** @hide */
89     public final boolean mMaxDistanceMmSet;
90 
91     /** @hide */
92     public final int mMaxDistanceMm;
93 
94     /** @hide */
SubscribeConfig(byte[] serviceName, byte[] serviceSpecificInfo, byte[] matchFilter, int subscribeType, int ttlSec, boolean enableTerminateNotification, boolean minDistanceMmSet, int minDistanceMm, boolean maxDistanceMmSet, int maxDistanceMm)95     public SubscribeConfig(byte[] serviceName, byte[] serviceSpecificInfo, byte[] matchFilter,
96             int subscribeType, int ttlSec, boolean enableTerminateNotification,
97             boolean minDistanceMmSet, int minDistanceMm, boolean maxDistanceMmSet,
98             int maxDistanceMm) {
99         mServiceName = serviceName;
100         mServiceSpecificInfo = serviceSpecificInfo;
101         mMatchFilter = matchFilter;
102         mSubscribeType = subscribeType;
103         mTtlSec = ttlSec;
104         mEnableTerminateNotification = enableTerminateNotification;
105         mMinDistanceMm = minDistanceMm;
106         mMinDistanceMmSet = minDistanceMmSet;
107         mMaxDistanceMm = maxDistanceMm;
108         mMaxDistanceMmSet = maxDistanceMmSet;
109     }
110 
111     @Override
toString()112     public String toString() {
113         return "SubscribeConfig [mServiceName='" + (mServiceName == null ? "<null>"
114                 : String.valueOf(HexEncoding.encode(mServiceName))) + ", mServiceName.length=" + (
115                 mServiceName == null ? 0 : mServiceName.length) + ", mServiceSpecificInfo='" + (
116                 (mServiceSpecificInfo == null) ? "<null>" : String.valueOf(
117                         HexEncoding.encode(mServiceSpecificInfo)))
118                 + ", mServiceSpecificInfo.length=" + (mServiceSpecificInfo == null ? 0
119                 : mServiceSpecificInfo.length) + ", mMatchFilter="
120                 + (new TlvBufferUtils.TlvIterable(0, 1, mMatchFilter)).toString()
121                 + ", mMatchFilter.length=" + (mMatchFilter == null ? 0 : mMatchFilter.length)
122                 + ", mSubscribeType=" + mSubscribeType + ", mTtlSec=" + mTtlSec
123                 + ", mEnableTerminateNotification=" + mEnableTerminateNotification
124                 + ", mMinDistanceMm=" + mMinDistanceMm
125                 + ", mMinDistanceMmSet=" + mMinDistanceMmSet
126                 + ", mMaxDistanceMm=" + mMaxDistanceMm
127                 + ", mMaxDistanceMmSet=" + mMaxDistanceMmSet + "]";
128     }
129 
130     @Override
describeContents()131     public int describeContents() {
132         return 0;
133     }
134 
135     @Override
writeToParcel(Parcel dest, int flags)136     public void writeToParcel(Parcel dest, int flags) {
137         dest.writeByteArray(mServiceName);
138         dest.writeByteArray(mServiceSpecificInfo);
139         dest.writeByteArray(mMatchFilter);
140         dest.writeInt(mSubscribeType);
141         dest.writeInt(mTtlSec);
142         dest.writeInt(mEnableTerminateNotification ? 1 : 0);
143         dest.writeInt(mMinDistanceMm);
144         dest.writeInt(mMinDistanceMmSet ? 1 : 0);
145         dest.writeInt(mMaxDistanceMm);
146         dest.writeInt(mMaxDistanceMmSet ? 1 : 0);
147     }
148 
149     public static final @android.annotation.NonNull Creator<SubscribeConfig> CREATOR = new Creator<SubscribeConfig>() {
150         @Override
151         public SubscribeConfig[] newArray(int size) {
152             return new SubscribeConfig[size];
153         }
154 
155         @Override
156         public SubscribeConfig createFromParcel(Parcel in) {
157             byte[] serviceName = in.createByteArray();
158             byte[] ssi = in.createByteArray();
159             byte[] matchFilter = in.createByteArray();
160             int subscribeType = in.readInt();
161             int ttlSec = in.readInt();
162             boolean enableTerminateNotification = in.readInt() != 0;
163             int minDistanceMm = in.readInt();
164             boolean minDistanceMmSet = in.readInt() != 0;
165             int maxDistanceMm = in.readInt();
166             boolean maxDistanceMmSet = in.readInt() != 0;
167 
168             return new SubscribeConfig(serviceName, ssi, matchFilter, subscribeType, ttlSec,
169                     enableTerminateNotification, minDistanceMmSet, minDistanceMm, maxDistanceMmSet,
170                     maxDistanceMm);
171         }
172     };
173 
174     @Override
equals(Object o)175     public boolean equals(Object o) {
176         if (this == o) {
177             return true;
178         }
179 
180         if (!(o instanceof SubscribeConfig)) {
181             return false;
182         }
183 
184         SubscribeConfig lhs = (SubscribeConfig) o;
185 
186         if (!(Arrays.equals(mServiceName, lhs.mServiceName) && Arrays.equals(
187                 mServiceSpecificInfo, lhs.mServiceSpecificInfo) && Arrays.equals(mMatchFilter,
188                 lhs.mMatchFilter) && mSubscribeType == lhs.mSubscribeType && mTtlSec == lhs.mTtlSec
189                 && mEnableTerminateNotification == lhs.mEnableTerminateNotification
190                 && mMinDistanceMmSet == lhs.mMinDistanceMmSet
191                 && mMaxDistanceMmSet == lhs.mMaxDistanceMmSet)) {
192             return false;
193         }
194 
195         if (mMinDistanceMmSet && mMinDistanceMm != lhs.mMinDistanceMm) {
196             return false;
197         }
198 
199         if (mMaxDistanceMmSet && mMaxDistanceMm != lhs.mMaxDistanceMm) {
200             return false;
201         }
202 
203         return true;
204     }
205 
206     @Override
hashCode()207     public int hashCode() {
208         int result = Objects.hash(Arrays.hashCode(mServiceName),
209                 Arrays.hashCode(mServiceSpecificInfo), Arrays.hashCode(mMatchFilter),
210                 mSubscribeType, mTtlSec, mEnableTerminateNotification, mMinDistanceMmSet,
211                 mMaxDistanceMmSet);
212 
213         if (mMinDistanceMmSet) {
214             result = Objects.hash(result, mMinDistanceMm);
215         }
216         if (mMaxDistanceMmSet) {
217             result = Objects.hash(result, mMaxDistanceMm);
218         }
219 
220         return result;
221     }
222 
223     /**
224      * Verifies that the contents of the SubscribeConfig are valid. Otherwise
225      * throws an IllegalArgumentException.
226      *
227      * @hide
228      */
assertValid(Characteristics characteristics, boolean rttSupported)229     public void assertValid(Characteristics characteristics, boolean rttSupported)
230             throws IllegalArgumentException {
231         WifiAwareUtils.validateServiceName(mServiceName);
232 
233         if (!TlvBufferUtils.isValid(mMatchFilter, 0, 1)) {
234             throw new IllegalArgumentException(
235                     "Invalid matchFilter configuration - LV fields do not match up to length");
236         }
237         if (mSubscribeType < SUBSCRIBE_TYPE_PASSIVE || mSubscribeType > SUBSCRIBE_TYPE_ACTIVE) {
238             throw new IllegalArgumentException("Invalid subscribeType - " + mSubscribeType);
239         }
240         if (mTtlSec < 0) {
241             throw new IllegalArgumentException("Invalid ttlSec - must be non-negative");
242         }
243 
244         if (characteristics != null) {
245             int maxServiceNameLength = characteristics.getMaxServiceNameLength();
246             if (maxServiceNameLength != 0 && mServiceName.length > maxServiceNameLength) {
247                 throw new IllegalArgumentException(
248                         "Service name longer than supported by device characteristics");
249             }
250             int maxServiceSpecificInfoLength = characteristics.getMaxServiceSpecificInfoLength();
251             if (maxServiceSpecificInfoLength != 0 && mServiceSpecificInfo != null
252                     && mServiceSpecificInfo.length > maxServiceSpecificInfoLength) {
253                 throw new IllegalArgumentException(
254                         "Service specific info longer than supported by device characteristics");
255             }
256             int maxMatchFilterLength = characteristics.getMaxMatchFilterLength();
257             if (maxMatchFilterLength != 0 && mMatchFilter != null
258                     && mMatchFilter.length > maxMatchFilterLength) {
259                 throw new IllegalArgumentException(
260                         "Match filter longer than supported by device characteristics");
261             }
262         }
263 
264         if (mMinDistanceMmSet && mMinDistanceMm < 0) {
265             throw new IllegalArgumentException("Minimum distance must be non-negative");
266         }
267         if (mMaxDistanceMmSet && mMaxDistanceMm < 0) {
268             throw new IllegalArgumentException("Maximum distance must be non-negative");
269         }
270         if (mMinDistanceMmSet && mMaxDistanceMmSet && mMaxDistanceMm <= mMinDistanceMm) {
271             throw new IllegalArgumentException(
272                     "Maximum distance must be greater than minimum distance");
273         }
274 
275         if (!rttSupported && (mMinDistanceMmSet || mMaxDistanceMmSet)) {
276             throw new IllegalArgumentException("Ranging is not supported");
277         }
278     }
279 
280     /**
281      * Builder used to build {@link SubscribeConfig} objects.
282      */
283     public static final class Builder {
284         private byte[] mServiceName;
285         private byte[] mServiceSpecificInfo;
286         private byte[] mMatchFilter;
287         private int mSubscribeType = SUBSCRIBE_TYPE_PASSIVE;
288         private int mTtlSec = 0;
289         private boolean mEnableTerminateNotification = true;
290         private boolean mMinDistanceMmSet = false;
291         private int mMinDistanceMm;
292         private boolean mMaxDistanceMmSet = false;
293         private int mMaxDistanceMm;
294 
295         /**
296          * Specify the service name of the subscribe session. The actual on-air
297          * value is a 6 byte hashed representation of this string.
298          * <p>
299          * The Service Name is a UTF-8 encoded string from 1 to 255 bytes in length.
300          * The only acceptable single-byte UTF-8 symbols for a Service Name are alphanumeric
301          * values (A-Z, a-z, 0-9), the hyphen ('-'), and the period ('.'). All valid multi-byte
302          * UTF-8 characters are acceptable in a Service Name.
303          * <p>
304          * Must be called - an empty ServiceName is not valid.
305          *
306          * @param serviceName The service name for the subscribe session.
307          *
308          * @return The builder to facilitate chaining
309          *         {@code builder.setXXX(..).setXXX(..)}.
310          */
setServiceName(@onNull String serviceName)311         public Builder setServiceName(@NonNull String serviceName) {
312             if (serviceName == null) {
313                 throw new IllegalArgumentException("Invalid service name - must be non-null");
314             }
315             mServiceName = serviceName.getBytes(StandardCharsets.UTF_8);
316             return this;
317         }
318 
319         /**
320          * Specify service specific information for the subscribe session. This is
321          * a free-form byte array available to the application to send
322          * additional information as part of the discovery operation - i.e. it
323          * will not be used to determine whether a publish/subscribe match
324          * occurs.
325          * <p>
326          *     Optional. Empty by default.
327          *
328          * @param serviceSpecificInfo A byte-array for the service-specific
329          *            information field.
330          *
331          * @return The builder to facilitate chaining
332          *         {@code builder.setXXX(..).setXXX(..)}.
333          */
setServiceSpecificInfo(@ullable byte[] serviceSpecificInfo)334         public Builder setServiceSpecificInfo(@Nullable byte[] serviceSpecificInfo) {
335             mServiceSpecificInfo = serviceSpecificInfo;
336             return this;
337         }
338 
339         /**
340          * The match filter for a subscribe session. Used to determine whether a service
341          * discovery occurred - in addition to relying on the service name.
342          * <p>
343          *     Optional. Empty by default.
344          *
345          * @param matchFilter A list of match filter entries (each of which is an arbitrary byte
346          *                    array).
347          *
348          * @return The builder to facilitate chaining
349          *         {@code builder.setXXX(..).setXXX(..)}.
350          */
setMatchFilter(@ullable List<byte[]> matchFilter)351         public Builder setMatchFilter(@Nullable List<byte[]> matchFilter) {
352             mMatchFilter = new TlvBufferUtils.TlvConstructor(0, 1).allocateAndPut(
353                     matchFilter).getArray();
354             return this;
355         }
356 
357         /**
358          * Sets the type of the subscribe session: active (subscribe packets are
359          * transmitted over-the-air), or passive (no subscribe packets are
360          * transmitted, a match is made against a solicited/active publish
361          * session whose packets are transmitted over-the-air).
362          *
363          * @param subscribeType Subscribe session type:
364          *            {@link SubscribeConfig#SUBSCRIBE_TYPE_ACTIVE} or
365          *            {@link SubscribeConfig#SUBSCRIBE_TYPE_PASSIVE}.
366          *
367          * @return The builder to facilitate chaining
368          *         {@code builder.setXXX(..).setXXX(..)}.
369          */
setSubscribeType(@ubscribeTypes int subscribeType)370         public Builder setSubscribeType(@SubscribeTypes int subscribeType) {
371             if (subscribeType < SUBSCRIBE_TYPE_PASSIVE || subscribeType > SUBSCRIBE_TYPE_ACTIVE) {
372                 throw new IllegalArgumentException("Invalid subscribeType - " + subscribeType);
373             }
374             mSubscribeType = subscribeType;
375             return this;
376         }
377 
378         /**
379          * Sets the time interval (in seconds) an active (
380          * {@link SubscribeConfig.Builder#setSubscribeType(int)}) subscribe session
381          * will be alive - i.e. broadcasting a packet. When the TTL is reached
382          * an event will be generated for
383          * {@link DiscoverySessionCallback#onSessionTerminated()}.
384          * <p>
385          *     Optional. 0 by default - indicating the session doesn't terminate on its own.
386          *     Session will be terminated when {@link DiscoverySession#close()} is
387          *     called.
388          *
389          * @param ttlSec Lifetime of a subscribe session in seconds.
390          *
391          * @return The builder to facilitate chaining
392          *         {@code builder.setXXX(..).setXXX(..)}.
393          */
setTtlSec(int ttlSec)394         public Builder setTtlSec(int ttlSec) {
395             if (ttlSec < 0) {
396                 throw new IllegalArgumentException("Invalid ttlSec - must be non-negative");
397             }
398             mTtlSec = ttlSec;
399             return this;
400         }
401 
402         /**
403          * Configure whether a subscribe terminate notification
404          * {@link DiscoverySessionCallback#onSessionTerminated()} is reported
405          * back to the callback.
406          *
407          * @param enable If true the terminate callback will be called when the
408          *            subscribe is terminated. Otherwise it will not be called.
409          *
410          * @return The builder to facilitate chaining
411          *         {@code builder.setXXX(..).setXXX(..)}.
412          */
setTerminateNotificationEnabled(boolean enable)413         public Builder setTerminateNotificationEnabled(boolean enable) {
414             mEnableTerminateNotification = enable;
415             return this;
416         }
417 
418         /**
419          * Configure the minimum distance to a discovered publisher at which to trigger a discovery
420          * notification. I.e. discovery will be triggered if we've found a matching publisher
421          * (based on the other criteria in this configuration) <b>and</b> the distance to the
422          * publisher is larger than the value specified in this API. Can be used in conjunction with
423          * {@link #setMaxDistanceMm(int)} to specify a geofence, i.e. discovery with min <=
424          * distance <= max.
425          * <p>
426          * For ranging to be used in discovery it must also be enabled on the publisher using
427          * {@link PublishConfig.Builder#setRangingEnabled(boolean)}. However, ranging may
428          * not be available or enabled on the publisher or may be temporarily disabled on either
429          * subscriber or publisher - in such cases discovery will proceed without ranging.
430          * <p>
431          * When ranging is enabled and available on both publisher and subscriber and a service
432          * is discovered based on geofence constraints the
433          * {@link DiscoverySessionCallback#onServiceDiscoveredWithinRange(PeerHandle, byte[], List, int)}
434          * is called, otherwise the
435          * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], List)}
436          * is called.
437          * <p>
438          * The device must support Wi-Fi RTT for this feature to be used. Feature support is checked
439          * as described in {@link android.net.wifi.rtt}.
440          *
441          * @param minDistanceMm Minimum distance, in mm, to the publisher above which to trigger
442          *                      discovery.
443          *
444          * @return The builder to facilitate chaining
445          *         {@code builder.setXXX(..).setXXX(..)}.
446          */
setMinDistanceMm(int minDistanceMm)447         public Builder setMinDistanceMm(int minDistanceMm) {
448             mMinDistanceMm = minDistanceMm;
449             mMinDistanceMmSet = true;
450             return this;
451         }
452 
453         /**
454          * Configure the maximum distance to a discovered publisher at which to trigger a discovery
455          * notification. I.e. discovery will be triggered if we've found a matching publisher
456          * (based on the other criteria in this configuration) <b>and</b> the distance to the
457          * publisher is smaller than the value specified in this API. Can be used in conjunction
458          * with {@link #setMinDistanceMm(int)} to specify a geofence, i.e. discovery with min <=
459          * distance <= max.
460          * <p>
461          * For ranging to be used in discovery it must also be enabled on the publisher using
462          * {@link PublishConfig.Builder#setRangingEnabled(boolean)}. However, ranging may
463          * not be available or enabled on the publisher or may be temporarily disabled on either
464          * subscriber or publisher - in such cases discovery will proceed without ranging.
465          * <p>
466          * When ranging is enabled and available on both publisher and subscriber and a service
467          * is discovered based on geofence constraints the
468          * {@link DiscoverySessionCallback#onServiceDiscoveredWithinRange(PeerHandle, byte[], List, int)}
469          * is called, otherwise the
470          * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], List)}
471          * is called.
472          * <p>
473          * The device must support Wi-Fi RTT for this feature to be used. Feature support is checked
474          * as described in {@link android.net.wifi.rtt}.
475          *
476          * @param maxDistanceMm Maximum distance, in mm, to the publisher below which to trigger
477          *                      discovery.
478          *
479          * @return The builder to facilitate chaining
480          *         {@code builder.setXXX(..).setXXX(..)}.
481          */
setMaxDistanceMm(int maxDistanceMm)482         public Builder setMaxDistanceMm(int maxDistanceMm) {
483             mMaxDistanceMm = maxDistanceMm;
484             mMaxDistanceMmSet = true;
485             return this;
486         }
487 
488         /**
489          * Build {@link SubscribeConfig} given the current requests made on the
490          * builder.
491          */
build()492         public SubscribeConfig build() {
493             return new SubscribeConfig(mServiceName, mServiceSpecificInfo, mMatchFilter,
494                     mSubscribeType, mTtlSec, mEnableTerminateNotification,
495                     mMinDistanceMmSet, mMinDistanceMm, mMaxDistanceMmSet, mMaxDistanceMm);
496         }
497     }
498 }
499