1 /*
2  * Copyright (C) 2017 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 static android.net.wifi.aware.WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR;
20 
21 import android.annotation.IntRange;
22 import android.annotation.NonNull;
23 import android.net.ConnectivityManager;
24 import android.net.Network;
25 import android.net.NetworkRequest;
26 import android.net.NetworkSpecifier;
27 import android.os.Build;
28 import android.os.Parcel;
29 import android.os.Parcelable;
30 import android.text.TextUtils;
31 
32 import androidx.annotation.RequiresApi;
33 
34 import com.android.modules.utils.build.SdkLevel;
35 
36 import java.util.Arrays;
37 import java.util.Objects;
38 
39 /**
40  * Network specifier object used to request a Wi-Fi Aware network. Apps should use the
41  * {@link WifiAwareNetworkSpecifier.Builder} class to create an instance.
42  */
43 public final class WifiAwareNetworkSpecifier extends NetworkSpecifier implements Parcelable {
44     /**
45      * TYPE: in band, specific peer: role, client_id, session_id, peer_id, pmk/passphrase optional
46      * @hide
47      */
48     public static final int NETWORK_SPECIFIER_TYPE_IB = 0;
49 
50     /**
51      * TYPE: in band, any peer: role, client_id, session_id, pmk/passphrase optional
52      * [only permitted for RESPONDER]
53      * @hide
54      */
55     public static final int NETWORK_SPECIFIER_TYPE_IB_ANY_PEER = 1;
56 
57     /**
58      * TYPE: out-of-band: role, client_id, peer_mac, pmk/passphrase optional
59      * @hide
60      */
61     public static final int NETWORK_SPECIFIER_TYPE_OOB = 2;
62 
63     /**
64      * TYPE: out-of-band, any peer: role, client_id, pmk/passphrase optional
65      * [only permitted for RESPONDER]
66      * @hide
67      */
68     public static final int NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER = 3;
69 
70     /** @hide */
71     public static final int NETWORK_SPECIFIER_TYPE_MAX_VALID = NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER;
72 
73     /**
74      * One of the NETWORK_SPECIFIER_TYPE_* constants. The type of the network specifier object.
75      * @hide
76      */
77     public final int type;
78 
79     /**
80      * The role of the device: WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR or
81      * WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER.
82      * @hide
83      */
84     public final int role;
85 
86     /**
87      * The client ID of the device.
88      * @hide
89      */
90     public final int clientId;
91 
92     /**
93      * The session ID in which context to request a data-path. Only relevant for IB requests.
94      * @hide
95      */
96     public final int sessionId;
97 
98     /**
99      * The peer ID of the device which the data-path should be connected to. Only relevant for
100      * IB requests (i.e. not IB_ANY_PEER or OOB*).
101      * @hide
102      */
103     public final int peerId;
104 
105     /**
106      * The peer MAC address of the device which the data-path should be connected to. Only relevant
107      * for OB requests (i.e. not OOB_ANY_PEER or IB*).
108      * @hide
109      */
110     public final byte[] peerMac;
111 
112     /**
113      * The PMK of the requested data-path. Can be null. Only one or none of pmk or passphrase should
114      * be specified.
115      * @hide
116      */
117     public final byte[] pmk;
118 
119     /**
120      * The Passphrase of the requested data-path. Can be null. Only one or none of the pmk or
121      * passphrase should be specified.
122      * @hide
123      */
124     public final String passphrase;
125 
126     /**
127      * The port information to be used for this link. This information will be communicated to the
128      * peer as part of the layer 2 link setup.
129      *
130      * Information only allowed on secure links since a single layer-2 link is set up for all
131      * requestors. Therefore if multiple apps on a single device request links to the same peer
132      * device they all get the same link. However, the link is only set up on the first request -
133      * hence only the first can transmit the port information. But we don't want to expose that
134      * information to other apps. Limiting to secure links would (usually) imply single app usage.
135      *
136      * @hide
137      */
138     public final int port;
139 
140     /**
141      * The transport protocol information to be used for this link. This information will be
142      * communicated to the peer as part of the layer 2 link setup.
143      *
144      * Information only allowed on secure links since a single layer-2 link is set up for all
145      * requestors. Therefore if multiple apps on a single device request links to the same peer
146      * device they all get the same link. However, the link is only set up on the first request -
147      * hence only the first can transmit the port information. But we don't want to expose that
148      * information to other apps. Limiting to secure links would (usually) imply single app usage.
149      *
150      * @hide
151      */
152     public final int transportProtocol;
153 
154     /** @hide */
WifiAwareNetworkSpecifier(int type, int role, int clientId, int sessionId, int peerId, byte[] peerMac, byte[] pmk, String passphrase, int port, int transportProtocol)155     public WifiAwareNetworkSpecifier(int type, int role, int clientId, int sessionId, int peerId,
156             byte[] peerMac, byte[] pmk, String passphrase, int port, int transportProtocol) {
157         this.type = type;
158         this.role = role;
159         this.clientId = clientId;
160         this.sessionId = sessionId;
161         this.peerId = peerId;
162         this.peerMac = peerMac;
163         this.pmk = pmk;
164         this.passphrase = passphrase;
165         this.port = port;
166         this.transportProtocol = transportProtocol;
167     }
168 
169     public static final @android.annotation.NonNull Creator<WifiAwareNetworkSpecifier> CREATOR =
170             new Creator<WifiAwareNetworkSpecifier>() {
171                 @Override
172                 public WifiAwareNetworkSpecifier createFromParcel(Parcel in) {
173                     return new WifiAwareNetworkSpecifier(
174                         in.readInt(), // type
175                         in.readInt(), // role
176                         in.readInt(), // clientId
177                         in.readInt(), // sessionId
178                         in.readInt(), // peerId
179                         in.createByteArray(), // peerMac
180                         in.createByteArray(), // pmk
181                         in.readString(), // passphrase
182                         in.readInt(), // port
183                         in.readInt()); // transportProtocol
184                 }
185 
186                 @Override
187                 public WifiAwareNetworkSpecifier[] newArray(int size) {
188                     return new WifiAwareNetworkSpecifier[size];
189                 }
190             };
191 
192     /**
193      * Indicates whether the network specifier specifies an OOB (out-of-band) data-path - i.e. a
194      * data-path created without a corresponding Aware discovery session.
195      *
196      * @hide
197      */
isOutOfBand()198     public boolean isOutOfBand() {
199         return type == NETWORK_SPECIFIER_TYPE_OOB || type == NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER;
200     }
201 
202     @Override
describeContents()203     public int describeContents() {
204         return 0;
205     }
206 
207     @Override
writeToParcel(Parcel dest, int flags)208     public void writeToParcel(Parcel dest, int flags) {
209         dest.writeInt(type);
210         dest.writeInt(role);
211         dest.writeInt(clientId);
212         dest.writeInt(sessionId);
213         dest.writeInt(peerId);
214         dest.writeByteArray(peerMac);
215         dest.writeByteArray(pmk);
216         dest.writeString(passphrase);
217         dest.writeInt(port);
218         dest.writeInt(transportProtocol);
219     }
220 
221     /** @hide */
222     @Override
canBeSatisfiedBy(NetworkSpecifier other)223     public boolean canBeSatisfiedBy(NetworkSpecifier other) {
224         // MatchAllNetworkSpecifier is taken care in NetworkCapabilities#satisfiedBySpecifier.
225         if (other instanceof WifiAwareAgentNetworkSpecifier) {
226             return ((WifiAwareAgentNetworkSpecifier) other).satisfiesAwareNetworkSpecifier(this);
227         }
228         return equals(other);
229     }
230 
231     /** @hide */
232     @Override
hashCode()233     public int hashCode() {
234         return Objects.hash(type, role, clientId, sessionId, peerId, Arrays.hashCode(peerMac),
235                 Arrays.hashCode(pmk), passphrase, port, transportProtocol);
236     }
237 
238     /** @hide */
239     @Override
equals(Object obj)240     public boolean equals(Object obj) {
241         if (this == obj) {
242             return true;
243         }
244 
245         if (!(obj instanceof WifiAwareNetworkSpecifier)) {
246             return false;
247         }
248 
249         WifiAwareNetworkSpecifier lhs = (WifiAwareNetworkSpecifier) obj;
250 
251         return type == lhs.type
252                 && role == lhs.role
253                 && clientId == lhs.clientId
254                 && sessionId == lhs.sessionId
255                 && peerId == lhs.peerId
256                 && Arrays.equals(peerMac, lhs.peerMac)
257                 && Arrays.equals(pmk, lhs.pmk)
258                 && Objects.equals(passphrase, lhs.passphrase)
259                 && port == lhs.port
260                 && transportProtocol == lhs.transportProtocol;
261     }
262 
263     /** @hide */
264     @Override
toString()265     public String toString() {
266         StringBuilder sb = new StringBuilder("WifiAwareNetworkSpecifier [");
267         sb.append("type=").append(type)
268                 .append(", role=").append(role)
269                 .append(", clientId=").append(clientId)
270                 .append(", sessionId=").append(sessionId)
271                 .append(", peerId=").append(peerId)
272                 // masking potential PII (although low impact information)
273                 .append(", peerMac=").append((peerMac == null) ? "<null>" : "<non-null>")
274                 // masking PII
275                 .append(", pmk=").append((pmk == null) ? "<null>" : "<non-null>")
276                 // masking PII
277                 .append(", passphrase=").append((passphrase == null) ? "<null>" : "<non-null>")
278                 .append(", port=").append(port).append(", transportProtocol=")
279                 .append(transportProtocol)
280                 .append("]");
281         return sb.toString();
282     }
283 
284     /**
285      * A builder class for a Wi-Fi Aware network specifier to set up an Aware connection with a
286      * peer.
287      */
288     public static final class Builder {
289         private DiscoverySession mDiscoverySession;
290         private PeerHandle mPeerHandle;
291         private String mPskPassphrase;
292         private byte[] mPmk;
293         private int mPort = 0; // invalid value
294         private int mTransportProtocol = -1; // invalid value
295 
296         /**
297          * Create a builder for {@link WifiAwareNetworkSpecifier} used in requests to set up a
298          * Wi-Fi Aware connection with a specific peer.
299          * <p>
300          * To set up a connection to any peer or to multiple peers use
301          * {@link #Builder(PublishDiscoverySession)}.
302          *
303          * @param discoverySession A Wi-Fi Aware discovery session in whose context the connection
304          *                         is created.
305          * @param peerHandle The handle of the peer to which the Wi-Fi Aware connection is
306          *                   requested. The peer is discovered through Wi-Fi Aware discovery. The
307          *                   handle can be obtained through
308          * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], java.util.List)}
309          *                   or
310          *                   {@link DiscoverySessionCallback#onMessageReceived(PeerHandle, byte[])}.
311          */
Builder(@onNull DiscoverySession discoverySession, @NonNull PeerHandle peerHandle)312         public Builder(@NonNull DiscoverySession discoverySession, @NonNull PeerHandle peerHandle) {
313             if (discoverySession == null) {
314                 throw new IllegalArgumentException("Non-null discoverySession required");
315             }
316             if (peerHandle == null) {
317                 throw new IllegalArgumentException("Non-null peerHandle required");
318             }
319             mDiscoverySession = discoverySession;
320             mPeerHandle = peerHandle;
321         }
322 
323         /**
324          * Create a builder for {@link WifiAwareNetworkSpecifier} used in requests to set up a
325          * Wi-Fi Aware connection. This configuration allows connections to any peers or to
326          * multiple peers (as opposed to only a specific peer with
327          * {@link #Builder(DiscoverySession, PeerHandle)}).
328          * <p>
329          * Multiple connections can be triggered by this configuration and using a single request
330          * via {@link ConnectivityManager#requestNetwork(NetworkRequest, ConnectivityManager.NetworkCallback)}
331          * and similar methods. Each successful connection will be signaled via the standard
332          * Connectivity Manager mechanisms -
333          * {@link ConnectivityManager.NetworkCallback#onAvailable(Network)}.
334          * Calling {@link ConnectivityManager#unregisterNetworkCallback(ConnectivityManager.NetworkCallback)}
335          * will terminate all connections.
336          */
337         @RequiresApi(Build.VERSION_CODES.S)
Builder(@onNull PublishDiscoverySession publishDiscoverySession)338         public Builder(@NonNull PublishDiscoverySession publishDiscoverySession) {
339             if (!SdkLevel.isAtLeastS()) {
340                 throw new UnsupportedOperationException();
341             }
342             if (publishDiscoverySession == null) {
343                 throw new IllegalArgumentException("Non-null publishDiscoverySession required");
344             }
345             mDiscoverySession = publishDiscoverySession;
346             mPeerHandle = null;
347         }
348 
349         /**
350          * Configure the PSK Passphrase for the Wi-Fi Aware connection being requested. This method
351          * is optional - if not called, then an Open (unencrypted) connection will be created.
352          *
353          * @param pskPassphrase The (optional) passphrase to be used to encrypt the link. Use the
354          *                      {@link #setPmk(byte[])} to specify a PMK.
355          * @return the current {@link Builder} builder, enabling chaining of builder
356          *         methods.
357          */
setPskPassphrase(@onNull String pskPassphrase)358         public @NonNull Builder setPskPassphrase(@NonNull String pskPassphrase) {
359             if (!WifiAwareUtils.validatePassphrase(pskPassphrase)) {
360                 throw new IllegalArgumentException("Passphrase must meet length requirements");
361             }
362             mPskPassphrase = pskPassphrase;
363             return this;
364         }
365 
366         /**
367          * Configure the PMK for the Wi-Fi Aware connection being requested. This method
368          * is optional - if not called, then an Open (unencrypted) connection will be created.
369          *
370          * @param pmk A PMK (pairwise master key, see IEEE 802.11i) specifying the key to use for
371          *            encrypting the data-path. Use the {@link #setPskPassphrase(String)} to
372          *            specify a Passphrase.
373          * @return the current {@link Builder} builder, enabling chaining of builder
374          *         methods.
375          */
setPmk(@onNull byte[] pmk)376         public @NonNull Builder setPmk(@NonNull byte[] pmk) {
377             if (!WifiAwareUtils.validatePmk(pmk)) {
378                 throw new IllegalArgumentException("PMK must 32 bytes");
379             }
380             mPmk = pmk;
381             return this;
382         }
383 
384         /**
385          * Configure the port number which will be used to create a connection over this link. This
386          * configuration should only be done on the server device, e.g. the device creating the
387          * {@link java.net.ServerSocket}.
388          * <p>Notes:
389          * <ul>
390          *     <li>The server device must be the Publisher device!
391          *     <li>The port information can only be specified on secure links, specified using
392          *     {@link #setPskPassphrase(String)} or {@link #setPmk(byte[])}.
393          * </ul>
394          *
395          * @param port A positive integer indicating the port to be used for communication.
396          * @return the current {@link Builder} builder, enabling chaining of builder
397          *         methods.
398          */
setPort(@ntRangefrom = 0, to = 65535) int port)399         public @NonNull Builder setPort(@IntRange(from = 0, to = 65535) int port) {
400             if (port <= 0 || port > 65535) {
401                 throw new IllegalArgumentException("The port must be a positive value (0, 65535]");
402             }
403             mPort = port;
404             return this;
405         }
406 
407         /**
408          * Configure the transport protocol which will be used to create a connection over this
409          * link. This configuration should only be done on the server device, e.g. the device
410          * creating the {@link java.net.ServerSocket} for TCP.
411          * <p>Notes:
412          * <ul>
413          *     <li>The server device must be the Publisher device!
414          *     <li>The transport protocol information can only be specified on secure links,
415          *     specified using {@link #setPskPassphrase(String)} or {@link #setPmk(byte[])}.
416          * </ul>
417          * The transport protocol number is assigned by the Internet Assigned Numbers Authority
418          * (IANA) https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml.
419          *
420          * @param transportProtocol The transport protocol to be used for communication.
421          * @return the current {@link Builder} builder, enabling chaining of builder
422          *         methods.
423          */
424         public @NonNull
setTransportProtocol(@ntRangefrom = 0, to = 255) int transportProtocol)425                 Builder setTransportProtocol(@IntRange(from = 0, to = 255) int transportProtocol) {
426             if (transportProtocol < 0 || transportProtocol > 255) {
427                 throw new IllegalArgumentException(
428                         "The transport protocol must be in range [0, 255]");
429             }
430             mTransportProtocol = transportProtocol;
431             return this;
432         }
433 
434         /**
435          * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)}
436          * for a WiFi Aware connection (link) to the specified peer. The
437          * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
438          * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
439          * <p> The default builder constructor will initialize a NetworkSpecifier which requests an
440          * open (non-encrypted) link. To request an encrypted link use the
441          * {@link #setPskPassphrase(String)} or {@link #setPmk(byte[])} builder methods.
442          *
443          * @return A {@link NetworkSpecifier} to be used to construct
444          * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass
445          * to {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
446          * android.net.ConnectivityManager.NetworkCallback)}
447          * [or other varieties of that API].
448          */
build()449         public @NonNull WifiAwareNetworkSpecifier build() {
450             if (mDiscoverySession == null) {
451                 throw new IllegalStateException("Null discovery session!?");
452             }
453             if (mPskPassphrase != null & mPmk != null) {
454                 throw new IllegalStateException(
455                         "Can only specify a Passphrase or a PMK - not both!");
456             }
457 
458             int role = mDiscoverySession instanceof SubscribeDiscoverySession
459                     ? WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
460                     : WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER;
461 
462             if (mPort != 0 || mTransportProtocol != -1) {
463                 if (role != WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) {
464                     throw new IllegalStateException(
465                             "Port and transport protocol information can only "
466                                     + "be specified on the Publisher device (which is the server");
467                 }
468                 if (TextUtils.isEmpty(mPskPassphrase) && mPmk == null) {
469                     throw new IllegalStateException("Port and transport protocol information can "
470                             + "only be specified on a secure link");
471                 }
472             }
473             int type = mPeerHandle == null
474                     ? WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB_ANY_PEER :
475                     WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB;
476 
477             return new WifiAwareNetworkSpecifier(type, role, mDiscoverySession.mClientId,
478                     mDiscoverySession.mSessionId, mPeerHandle != null ? mPeerHandle.peerId : 0,
479                     null, mPmk, mPskPassphrase, mPort, mTransportProtocol);
480         }
481     }
482 }
483