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
17package android.net.wifi.aware;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.annotation.SystemApi;
22import android.net.NetworkSpecifier;
23import android.net.wifi.RttManager;
24import android.util.Log;
25
26import dalvik.system.CloseGuard;
27
28import java.lang.ref.WeakReference;
29
30/**
31 * A class representing a single publish or subscribe Aware session. This object
32 * will not be created directly - only its child classes are available:
33 * {@link PublishDiscoverySession} and {@link SubscribeDiscoverySession}. This
34 * class provides functionality common to both publish and subscribe discovery sessions:
35 * <ul>
36 *     <li>Sending messages: {@link #sendMessage(PeerHandle, int, byte[])} method.
37 *     <li>Creating a network-specifier when requesting a Aware connection:
38 *     {@link #createNetworkSpecifierOpen(PeerHandle)} or
39 *     {@link #createNetworkSpecifierPassphrase(PeerHandle, String)}.
40 * </ul>
41 * The {@link #close()} method must be called to destroy discovery sessions once they are
42 * no longer needed.
43 */
44public class DiscoverySession implements AutoCloseable {
45    private static final String TAG = "DiscoverySession";
46    private static final boolean DBG = false;
47    private static final boolean VDBG = false; // STOPSHIP if true
48
49    private static final int MAX_SEND_RETRY_COUNT = 5;
50
51    /** @hide */
52    protected WeakReference<WifiAwareManager> mMgr;
53    /** @hide */
54    protected final int mClientId;
55    /** @hide */
56    protected final int mSessionId;
57    /** @hide */
58    protected boolean mTerminated = false;
59
60    private final CloseGuard mCloseGuard = CloseGuard.get();
61
62    /**
63     * Return the maximum permitted retry count when sending messages using
64     * {@link #sendMessage(PeerHandle, int, byte[], int)}.
65     *
66     * @return Maximum retry count when sending messages.
67     *
68     * @hide
69     */
70    public static int getMaxSendRetryCount() {
71        return MAX_SEND_RETRY_COUNT;
72    }
73
74    /** @hide */
75    public DiscoverySession(WifiAwareManager manager, int clientId, int sessionId) {
76        if (VDBG) {
77            Log.v(TAG, "New discovery session created: manager=" + manager + ", clientId="
78                    + clientId + ", sessionId=" + sessionId);
79        }
80
81        mMgr = new WeakReference<>(manager);
82        mClientId = clientId;
83        mSessionId = sessionId;
84
85        mCloseGuard.open("destroy");
86    }
87
88    /**
89     * Destroy the publish or subscribe session - free any resources, and stop
90     * transmitting packets on-air (for an active session) or listening for
91     * matches (for a passive session). The session may not be used for any
92     * additional operations after its destruction.
93     * <p>
94     *     This operation must be done on a session which is no longer needed. Otherwise system
95     *     resources will continue to be utilized until the application exits. The only
96     *     exception is a session for which we received a termination callback,
97     *     {@link DiscoverySessionCallback#onSessionTerminated()}.
98     */
99    @Override
100    public void close() {
101        WifiAwareManager mgr = mMgr.get();
102        if (mgr == null) {
103            Log.w(TAG, "destroy: called post GC on WifiAwareManager");
104            return;
105        }
106        mgr.terminateSession(mClientId, mSessionId);
107        mTerminated = true;
108        mMgr.clear();
109        mCloseGuard.close();
110    }
111
112    /**
113     * Sets the status of the session to terminated - i.e. an indication that
114     * already terminated rather than executing a termination.
115     *
116     * @hide
117     */
118    public void setTerminated() {
119        if (mTerminated) {
120            Log.w(TAG, "terminate: already terminated.");
121            return;
122        }
123
124        mTerminated = true;
125        mMgr.clear();
126        mCloseGuard.close();
127    }
128
129    /** @hide */
130    @Override
131    protected void finalize() throws Throwable {
132        try {
133            if (!mTerminated) {
134                mCloseGuard.warnIfOpen();
135                close();
136            }
137        } finally {
138            super.finalize();
139        }
140    }
141
142    /**
143     * Sends a message to the specified destination. Aware messages are transmitted in the context
144     * of a discovery session - executed subsequent to a publish/subscribe
145     * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle,
146     * byte[], java.util.List)} event.
147     * <p>
148     *     Aware messages are not guaranteed delivery. Callbacks on
149     *     {@link DiscoverySessionCallback} indicate message was transmitted successfully,
150     *     {@link DiscoverySessionCallback#onMessageSendSucceeded(int)}, or transmission
151     *     failed (possibly after several retries) -
152     *     {@link DiscoverySessionCallback#onMessageSendFailed(int)}.
153     * <p>
154     *     The peer will get a callback indicating a message was received using
155     *     {@link DiscoverySessionCallback#onMessageReceived(PeerHandle,
156     *     byte[])}.
157     *
158     * @param peerHandle The peer's handle for the message. Must be a result of an
159     * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle,
160     * byte[], java.util.List)} or
161     * {@link DiscoverySessionCallback#onMessageReceived(PeerHandle,
162     * byte[])} events.
163     * @param messageId An arbitrary integer used by the caller to identify the message. The same
164     *            integer ID will be returned in the callbacks indicating message send success or
165     *            failure. The {@code messageId} is not used internally by the Aware service - it
166     *                  can be arbitrary and non-unique.
167     * @param message The message to be transmitted.
168     * @param retryCount An integer specifying how many additional service-level (as opposed to PHY
169     *            or MAC level) retries should be attempted if there is no ACK from the receiver
170     *            (note: no retransmissions are attempted in other failure cases). A value of 0
171     *            indicates no retries. Max permitted value is {@link #getMaxSendRetryCount()}.
172     *
173     * @hide
174     */
175    public void sendMessage(@NonNull PeerHandle peerHandle, int messageId,
176            @Nullable byte[] message, int retryCount) {
177        if (mTerminated) {
178            Log.w(TAG, "sendMessage: called on terminated session");
179            return;
180        }
181
182        WifiAwareManager mgr = mMgr.get();
183        if (mgr == null) {
184            Log.w(TAG, "sendMessage: called post GC on WifiAwareManager");
185            return;
186        }
187
188        mgr.sendMessage(mClientId, mSessionId, peerHandle, message, messageId, retryCount);
189    }
190
191    /**
192     * Sends a message to the specified destination. Aware messages are transmitted in the context
193     * of a discovery session - executed subsequent to a publish/subscribe
194     * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle,
195     * byte[], java.util.List)} event.
196     * <p>
197     *     Aware messages are not guaranteed delivery. Callbacks on
198     *     {@link DiscoverySessionCallback} indicate message was transmitted successfully,
199     *     {@link DiscoverySessionCallback#onMessageSendSucceeded(int)}, or transmission
200     *     failed (possibly after several retries) -
201     *     {@link DiscoverySessionCallback#onMessageSendFailed(int)}.
202     * <p>
203     * The peer will get a callback indicating a message was received using
204     * {@link DiscoverySessionCallback#onMessageReceived(PeerHandle,
205     * byte[])}.
206     *
207     * @param peerHandle The peer's handle for the message. Must be a result of an
208     * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle,
209     * byte[], java.util.List)} or
210     * {@link DiscoverySessionCallback#onMessageReceived(PeerHandle,
211     * byte[])} events.
212     * @param messageId An arbitrary integer used by the caller to identify the message. The same
213     *            integer ID will be returned in the callbacks indicating message send success or
214     *            failure. The {@code messageId} is not used internally by the Aware service - it
215     *                  can be arbitrary and non-unique.
216     * @param message The message to be transmitted.
217     */
218    public void sendMessage(@NonNull PeerHandle peerHandle, int messageId,
219            @Nullable byte[] message) {
220        sendMessage(peerHandle, messageId, message, 0);
221    }
222
223    /**
224     * Start a ranging operation with the specified peers. The peer IDs are obtained from an
225     * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle,
226     * byte[], java.util.List)} or
227     * {@link DiscoverySessionCallback#onMessageReceived(PeerHandle,
228     * byte[])} operation - can
229     * only range devices which are part of an ongoing discovery session.
230     *
231     * @param params   RTT parameters - each corresponding to a specific peer ID (the array sizes
232     *                 must be identical). The
233     *                 {@link android.net.wifi.RttManager.RttParams#bssid} member must be set to
234     *                 a peer ID - not to a MAC address.
235     * @param listener The listener to receive the results of the ranging session.
236     * @hide
237     * [TODO: b/28847998 - track RTT API & visilibity]
238     */
239    public void startRanging(RttManager.RttParams[] params, RttManager.RttListener listener) {
240        if (mTerminated) {
241            Log.w(TAG, "startRanging: called on terminated session");
242            return;
243        }
244
245        WifiAwareManager mgr = mMgr.get();
246        if (mgr == null) {
247            Log.w(TAG, "startRanging: called post GC on WifiAwareManager");
248            return;
249        }
250
251        mgr.startRanging(mClientId, mSessionId, params, listener);
252    }
253
254    /**
255     * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} for
256     * an unencrypted WiFi Aware connection (link) to the specified peer. The
257     * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
258     * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
259     * <p>
260     * This method should be used when setting up a connection with a peer discovered through Aware
261     * discovery or communication (in such scenarios the MAC address of the peer is shielded by
262     * an opaque peer ID handle). If an Aware connection is needed to a peer discovered using other
263     * OOB (out-of-band) mechanism then use the alternative
264     * {@link WifiAwareSession#createNetworkSpecifierOpen(int, byte[])} method - which uses the
265     * peer's MAC address.
266     * <p>
267     * Note: per the Wi-Fi Aware specification the roles are fixed - a Subscriber is an INITIATOR
268     * and a Publisher is a RESPONDER.
269     * <p>
270     * To set up an encrypted link use the
271     * {@link #createNetworkSpecifierPassphrase(PeerHandle, String)} API.
272     *
273     * @param peerHandle The peer's handle obtained through
274     * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], java.util.List)}
275     *                   or
276     *                   {@link DiscoverySessionCallback#onMessageReceived(PeerHandle, byte[])}.
277     *                   On a RESPONDER this value is used to gate the acceptance of a connection
278     *                   request from only that peer. A RESPONDER may specify a {@code null} -
279     *                   indicating that it will accept connection requests from any device.
280     *
281     * @return A {@link NetworkSpecifier} to be used to construct
282     * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass to
283     * {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
284     * android.net.ConnectivityManager.NetworkCallback)}
285     * [or other varieties of that API].
286     */
287    public NetworkSpecifier createNetworkSpecifierOpen(@Nullable PeerHandle peerHandle) {
288        if (mTerminated) {
289            Log.w(TAG, "createNetworkSpecifierOpen: called on terminated session");
290            return null;
291        }
292
293        WifiAwareManager mgr = mMgr.get();
294        if (mgr == null) {
295            Log.w(TAG, "createNetworkSpecifierOpen: called post GC on WifiAwareManager");
296            return null;
297        }
298
299        int role = this instanceof SubscribeDiscoverySession
300                ? WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
301                : WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER;
302
303        return mgr.createNetworkSpecifier(mClientId, role, mSessionId, peerHandle, null, null);
304    }
305
306    /**
307     * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} for
308     * an encrypted WiFi Aware connection (link) to the specified peer. The
309     * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
310     * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
311     * <p>
312     * This method should be used when setting up a connection with a peer discovered through Aware
313     * discovery or communication (in such scenarios the MAC address of the peer is shielded by
314     * an opaque peer ID handle). If an Aware connection is needed to a peer discovered using other
315     * OOB (out-of-band) mechanism then use the alternative
316     * {@link WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String)} method -
317     * which uses the peer's MAC address.
318     * <p>
319     * Note: per the Wi-Fi Aware specification the roles are fixed - a Subscriber is an INITIATOR
320     * and a Publisher is a RESPONDER.
321     *
322     * @param peerHandle The peer's handle obtained through
323     * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle,
324     * byte[], java.util.List)} or
325     * {@link DiscoverySessionCallback#onMessageReceived(PeerHandle,
326     * byte[])}. On a RESPONDER this value is used to gate the acceptance of a connection request
327     *                   from only that peer. A RESPONDER may specify a {@code null} - indicating
328     *                   that it will accept connection requests from any device.
329     * @param passphrase The passphrase to be used to encrypt the link. The PMK is generated from
330     *                   the passphrase. Use the
331     *                   {@link #createNetworkSpecifierOpen(PeerHandle)} API to
332     *                   specify an open (unencrypted) link.
333     *
334     * @return A {@link NetworkSpecifier} to be used to construct
335     * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass to
336     * {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
337     * android.net.ConnectivityManager.NetworkCallback)}
338     * [or other varieties of that API].
339     */
340    public NetworkSpecifier createNetworkSpecifierPassphrase(
341            @Nullable PeerHandle peerHandle, @NonNull String passphrase) {
342        if (passphrase == null || passphrase.length() == 0) {
343            throw new IllegalArgumentException("Passphrase must not be null or empty");
344        }
345
346        if (mTerminated) {
347            Log.w(TAG, "createNetworkSpecifierPassphrase: called on terminated session");
348            return null;
349        }
350
351        WifiAwareManager mgr = mMgr.get();
352        if (mgr == null) {
353            Log.w(TAG, "createNetworkSpecifierPassphrase: called post GC on WifiAwareManager");
354            return null;
355        }
356
357        int role = this instanceof SubscribeDiscoverySession
358                ? WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
359                : WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER;
360
361        return mgr.createNetworkSpecifier(mClientId, role, mSessionId, peerHandle, null,
362                passphrase);
363    }
364
365    /**
366     * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} for
367     * an encrypted WiFi Aware connection (link) to the specified peer. The
368     * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
369     * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
370     * <p>
371     * This method should be used when setting up a connection with a peer discovered through Aware
372     * discovery or communication (in such scenarios the MAC address of the peer is shielded by
373     * an opaque peer ID handle). If an Aware connection is needed to a peer discovered using other
374     * OOB (out-of-band) mechanism then use the alternative
375     * {@link WifiAwareSession#createNetworkSpecifierPmk(int, byte[], byte[])} method - which uses
376     * the peer's MAC address.
377     * <p>
378     * Note: per the Wi-Fi Aware specification the roles are fixed - a Subscriber is an INITIATOR
379     * and a Publisher is a RESPONDER.
380     *
381     * @param peerHandle The peer's handle obtained through
382     * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle,
383     * byte[], java.util.List)} or
384     * {@link DiscoverySessionCallback#onMessageReceived(PeerHandle,
385     * byte[])}. On a RESPONDER this value is used to gate the acceptance of a connection request
386     *                   from only that peer. A RESPONDER may specify a null - indicating that
387     *                   it will accept connection requests from any device.
388     * @param pmk A PMK (pairwise master key, see IEEE 802.11i) specifying the key to use for
389     *            encrypting the data-path. Use the
390     *            {@link #createNetworkSpecifierPassphrase(PeerHandle, String)} to specify a
391     *            Passphrase or {@link #createNetworkSpecifierOpen(PeerHandle)} to specify an
392     *            open (unencrypted) link.
393     *
394     * @return A {@link NetworkSpecifier} to be used to construct
395     * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass to
396     * {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
397     * android.net.ConnectivityManager.NetworkCallback)}
398     * [or other varieties of that API].
399     *
400     * @hide
401     */
402    @SystemApi
403    public NetworkSpecifier createNetworkSpecifierPmk(@Nullable PeerHandle peerHandle,
404            @NonNull byte[] pmk) {
405        if (pmk == null || pmk.length == 0) {
406            throw new IllegalArgumentException("PMK must not be null or empty");
407        }
408
409        if (mTerminated) {
410            Log.w(TAG, "createNetworkSpecifierPmk: called on terminated session");
411            return null;
412        }
413
414        WifiAwareManager mgr = mMgr.get();
415        if (mgr == null) {
416            Log.w(TAG, "createNetworkSpecifierPmk: called post GC on WifiAwareManager");
417            return null;
418        }
419
420        int role = this instanceof SubscribeDiscoverySession
421                ? WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
422                : WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER;
423
424        return mgr.createNetworkSpecifier(mClientId, role, mSessionId, peerHandle, pmk, null);
425    }
426}
427