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 com.android.server.wifi.aware;
18
19import android.hardware.wifi.V1_0.NanStatusType;
20import android.net.wifi.aware.IWifiAwareDiscoverySessionCallback;
21import android.net.wifi.aware.PublishConfig;
22import android.net.wifi.aware.SubscribeConfig;
23import android.os.RemoteException;
24import android.util.Log;
25import android.util.SparseArray;
26
27import libcore.util.HexEncoding;
28
29import java.io.FileDescriptor;
30import java.io.PrintWriter;
31import java.util.Arrays;
32
33/**
34 * Manages the state of a single Aware discovery session (publish or subscribe).
35 * Primary state consists of a callback through which session callbacks are
36 * executed as well as state related to currently active discovery sessions:
37 * publish/subscribe ID, and MAC address caching (hiding) from clients.
38 */
39public class WifiAwareDiscoverySessionState {
40    private static final String TAG = "WifiAwareDiscSessState";
41    private static final boolean VDBG = false; // STOPSHIP if true
42    /* package */ boolean mDbg = false;
43
44    private static int sNextPeerIdToBeAllocated = 100; // used to create a unique peer ID
45
46    private final WifiAwareNativeApi mWifiAwareNativeApi;
47    private int mSessionId;
48    private byte mPubSubId;
49    private IWifiAwareDiscoverySessionCallback mCallback;
50    private boolean mIsPublishSession;
51    private boolean mIsRangingEnabled;
52    private final long mCreationTime;
53
54    static class PeerInfo {
55        PeerInfo(int instanceId, byte[] mac) {
56            mInstanceId = instanceId;
57            mMac = mac;
58        }
59
60        int mInstanceId;
61        byte[] mMac;
62
63        @Override
64        public String toString() {
65            StringBuilder sb = new StringBuilder("instanceId [");
66            sb.append(mInstanceId).append(", mac=").append(HexEncoding.encode(mMac)).append("]");
67            return sb.toString();
68        }
69    }
70
71    private final SparseArray<PeerInfo> mPeerInfoByRequestorInstanceId = new SparseArray<>();
72
73    public WifiAwareDiscoverySessionState(WifiAwareNativeApi wifiAwareNativeApi, int sessionId,
74            byte pubSubId, IWifiAwareDiscoverySessionCallback callback, boolean isPublishSession,
75            boolean isRangingEnabled, long creationTime) {
76        mWifiAwareNativeApi = wifiAwareNativeApi;
77        mSessionId = sessionId;
78        mPubSubId = pubSubId;
79        mCallback = callback;
80        mIsPublishSession = isPublishSession;
81        mIsRangingEnabled = isRangingEnabled;
82        mCreationTime = creationTime;
83    }
84
85    public int getSessionId() {
86        return mSessionId;
87    }
88
89    public int getPubSubId() {
90        return mPubSubId;
91    }
92
93    public boolean isPublishSession() {
94        return mIsPublishSession;
95    }
96
97    public boolean isRangingEnabled() {
98        return mIsRangingEnabled;
99    }
100
101    public long getCreationTime() {
102        return mCreationTime;
103    }
104
105    public IWifiAwareDiscoverySessionCallback getCallback() {
106        return mCallback;
107    }
108
109    /**
110     * Return the peer information of the specified peer ID - or a null if no such peer ID is
111     * registered.
112     */
113    public PeerInfo getPeerInfo(int peerId) {
114        return mPeerInfoByRequestorInstanceId.get(peerId);
115    }
116
117    /**
118     * Destroy the current discovery session - stops publishing or subscribing
119     * if currently active.
120     */
121    public void terminate() {
122        mCallback = null;
123
124        if (mIsPublishSession) {
125            mWifiAwareNativeApi.stopPublish((short) 0, mPubSubId);
126        } else {
127            mWifiAwareNativeApi.stopSubscribe((short) 0, mPubSubId);
128        }
129    }
130
131    /**
132     * Indicates whether the publish/subscribe ID (a HAL ID) corresponds to this
133     * session.
134     *
135     * @param pubSubId The publish/subscribe HAL ID to be tested.
136     * @return true if corresponds to this session, false otherwise.
137     */
138    public boolean isPubSubIdSession(int pubSubId) {
139        return mPubSubId == pubSubId;
140    }
141
142    /**
143     * Modify a publish discovery session.
144     *
145     * @param transactionId Transaction ID for the transaction - used in the
146     *            async callback to match with the original request.
147     * @param config Configuration of the publish session.
148     */
149    public boolean updatePublish(short transactionId, PublishConfig config) {
150        if (!mIsPublishSession) {
151            Log.e(TAG, "A SUBSCRIBE session is being used to publish");
152            try {
153                mCallback.onSessionConfigFail(NanStatusType.INTERNAL_FAILURE);
154            } catch (RemoteException e) {
155                Log.e(TAG, "updatePublish: RemoteException=" + e);
156            }
157            return false;
158        }
159
160        boolean success = mWifiAwareNativeApi.publish(transactionId, mPubSubId, config);
161        if (!success) {
162            try {
163                mCallback.onSessionConfigFail(NanStatusType.INTERNAL_FAILURE);
164            } catch (RemoteException e) {
165                Log.w(TAG, "updatePublish onSessionConfigFail(): RemoteException (FYI): " + e);
166            }
167        }
168
169        return success;
170    }
171
172    /**
173     * Modify a subscribe discovery session.
174     *
175     * @param transactionId Transaction ID for the transaction - used in the
176     *            async callback to match with the original request.
177     * @param config Configuration of the subscribe session.
178     */
179    public boolean updateSubscribe(short transactionId, SubscribeConfig config) {
180        if (mIsPublishSession) {
181            Log.e(TAG, "A PUBLISH session is being used to subscribe");
182            try {
183                mCallback.onSessionConfigFail(NanStatusType.INTERNAL_FAILURE);
184            } catch (RemoteException e) {
185                Log.e(TAG, "updateSubscribe: RemoteException=" + e);
186            }
187            return false;
188        }
189
190        boolean success = mWifiAwareNativeApi.subscribe(transactionId, mPubSubId, config);
191        if (!success) {
192            try {
193                mCallback.onSessionConfigFail(NanStatusType.INTERNAL_FAILURE);
194            } catch (RemoteException e) {
195                Log.w(TAG, "updateSubscribe onSessionConfigFail(): RemoteException (FYI): " + e);
196            }
197        }
198
199        return success;
200    }
201
202    /**
203     * Send a message to a peer which is part of a discovery session.
204     *
205     * @param transactionId Transaction ID for the transaction - used in the
206     *            async callback to match with the original request.
207     * @param peerId ID of the peer. Obtained through previous communication (a
208     *            match indication).
209     * @param message Message byte array to send to the peer.
210     * @param messageId A message ID provided by caller to be used in any
211     *            callbacks related to the message (success/failure).
212     */
213    public boolean sendMessage(short transactionId, int peerId, byte[] message, int messageId) {
214        PeerInfo peerInfo = mPeerInfoByRequestorInstanceId.get(peerId);
215        if (peerInfo == null) {
216            Log.e(TAG, "sendMessage: attempting to send a message to an address which didn't "
217                    + "match/contact us");
218            try {
219                mCallback.onMessageSendFail(messageId, NanStatusType.INTERNAL_FAILURE);
220            } catch (RemoteException e) {
221                Log.e(TAG, "sendMessage: RemoteException=" + e);
222            }
223            return false;
224        }
225
226        boolean success = mWifiAwareNativeApi.sendMessage(transactionId, mPubSubId,
227                peerInfo.mInstanceId, peerInfo.mMac, message, messageId);
228        if (!success) {
229            try {
230                mCallback.onMessageSendFail(messageId, NanStatusType.INTERNAL_FAILURE);
231            } catch (RemoteException e) {
232                Log.e(TAG, "sendMessage: RemoteException=" + e);
233            }
234            return false;
235        }
236
237        return success;
238    }
239
240    /**
241     * Callback from HAL when a discovery occurs - i.e. when a match to an
242     * active subscription request or to a solicited publish request occurs.
243     * Propagates to client if registered.
244     *
245     * @param requestorInstanceId The ID used to identify the peer in this
246     *            matched session.
247     * @param peerMac The MAC address of the peer. Never propagated to client
248     *            due to privacy concerns.
249     * @param serviceSpecificInfo Information from the discovery advertisement
250     *            (usually not used in the match decisions).
251     * @param matchFilter The filter from the discovery advertisement (which was
252     *            used in the match decision).
253     * @param rangingIndication Bit mask indicating the type of ranging event triggered.
254     * @param rangeMm The range to the peer in mm (valid if rangingIndication specifies ingress
255     *                or egress events - i.e. non-zero).
256     */
257    public void onMatch(int requestorInstanceId, byte[] peerMac, byte[] serviceSpecificInfo,
258            byte[] matchFilter, int rangingIndication, int rangeMm) {
259        int peerId = getPeerIdOrAddIfNew(requestorInstanceId, peerMac);
260
261        try {
262            if (rangingIndication == 0) {
263                mCallback.onMatch(peerId, serviceSpecificInfo, matchFilter);
264            } else {
265                mCallback.onMatchWithDistance(peerId, serviceSpecificInfo, matchFilter, rangeMm);
266            }
267        } catch (RemoteException e) {
268            Log.w(TAG, "onMatch: RemoteException (FYI): " + e);
269        }
270    }
271
272    /**
273     * Callback from HAL when a message is received from a peer in a discovery
274     * session. Propagated to client if registered.
275     *
276     * @param requestorInstanceId An ID used to identify the peer.
277     * @param peerMac The MAC address of the peer sending the message. This
278     *            information is never propagated to the client due to privacy
279     *            concerns.
280     * @param message The received message.
281     */
282    public void onMessageReceived(int requestorInstanceId, byte[] peerMac, byte[] message) {
283        int peerId = getPeerIdOrAddIfNew(requestorInstanceId, peerMac);
284
285        try {
286            mCallback.onMessageReceived(peerId, message);
287        } catch (RemoteException e) {
288            Log.w(TAG, "onMessageReceived: RemoteException (FYI): " + e);
289        }
290    }
291
292    private int getPeerIdOrAddIfNew(int requestorInstanceId, byte[] peerMac) {
293        for (int i = 0; i < mPeerInfoByRequestorInstanceId.size(); ++i) {
294            PeerInfo peerInfo = mPeerInfoByRequestorInstanceId.valueAt(i);
295            if (peerInfo.mInstanceId == requestorInstanceId && Arrays.equals(peerMac,
296                    peerInfo.mMac)) {
297                return mPeerInfoByRequestorInstanceId.keyAt(i);
298            }
299        }
300
301        int newPeerId = sNextPeerIdToBeAllocated++;
302        PeerInfo newPeerInfo = new PeerInfo(requestorInstanceId, peerMac);
303        mPeerInfoByRequestorInstanceId.put(newPeerId, newPeerInfo);
304
305        if (VDBG) {
306            Log.v(TAG, "New peer info: peerId=" + newPeerId + ", peerInfo=" + newPeerInfo);
307        }
308
309        return newPeerId;
310    }
311
312    /**
313     * Dump the internal state of the class.
314     */
315    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
316        pw.println("AwareSessionState:");
317        pw.println("  mSessionId: " + mSessionId);
318        pw.println("  mIsPublishSession: " + mIsPublishSession);
319        pw.println("  mPubSubId: " + mPubSubId);
320        pw.println("  mPeerInfoByRequestorInstanceId: [" + mPeerInfoByRequestorInstanceId + "]");
321    }
322}
323