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