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.content.Context;
20import android.content.pm.PackageManager;
21import android.hardware.wifi.V1_0.NanStatusType;
22import android.net.wifi.RttManager;
23import android.net.wifi.aware.Characteristics;
24import android.net.wifi.aware.ConfigRequest;
25import android.net.wifi.aware.DiscoverySession;
26import android.net.wifi.aware.IWifiAwareDiscoverySessionCallback;
27import android.net.wifi.aware.IWifiAwareEventCallback;
28import android.net.wifi.aware.IWifiAwareManager;
29import android.net.wifi.aware.PublishConfig;
30import android.net.wifi.aware.SubscribeConfig;
31import android.os.Binder;
32import android.os.HandlerThread;
33import android.os.IBinder;
34import android.os.RemoteException;
35import android.util.Log;
36import android.util.SparseArray;
37import android.util.SparseIntArray;
38
39import java.io.FileDescriptor;
40import java.io.PrintWriter;
41import java.util.Arrays;
42
43/**
44 * Implementation of the IWifiAwareManager AIDL interface. Performs validity
45 * (permission and clientID-UID mapping) checks and delegates execution to the
46 * WifiAwareStateManager singleton handler. Limited state to feedback which has to
47 * be provided instantly: client and session IDs.
48 */
49public class WifiAwareServiceImpl extends IWifiAwareManager.Stub {
50    private static final String TAG = "WifiAwareService";
51    private static final boolean DBG = false;
52    private static final boolean VDBG = false; // STOPSHIP if true
53
54    private Context mContext;
55    private WifiAwareStateManager mStateManager;
56
57    private final Object mLock = new Object();
58    private final SparseArray<IBinder.DeathRecipient> mDeathRecipientsByClientId =
59            new SparseArray<>();
60    private int mNextClientId = 1;
61    private int mNextRangingId = 1;
62    private final SparseIntArray mUidByClientId = new SparseIntArray();
63
64    public WifiAwareServiceImpl(Context context) {
65        mContext = context.getApplicationContext();
66    }
67
68    /**
69     * Proxy for the final native call of the parent class. Enables mocking of
70     * the function.
71     */
72    public int getMockableCallingUid() {
73        return getCallingUid();
74    }
75
76    /**
77     * Start the service: allocate a new thread (for now), start the handlers of
78     * the components of the service.
79     */
80    public void start(HandlerThread handlerThread, WifiAwareStateManager awareStateManager) {
81        Log.i(TAG, "Starting Wi-Fi Aware service");
82
83        mStateManager = awareStateManager;
84        mStateManager.start(mContext, handlerThread.getLooper());
85    }
86
87    /**
88     * Start/initialize portions of the service which require the boot stage to be complete.
89     */
90    public void startLate() {
91        Log.i(TAG, "Late initialization of Wi-Fi Aware service");
92
93        mStateManager.startLate();
94    }
95
96    @Override
97    public boolean isUsageEnabled() {
98        enforceAccessPermission();
99
100        return mStateManager.isUsageEnabled();
101    }
102
103    @Override
104    public Characteristics getCharacteristics() {
105        enforceAccessPermission();
106
107        return mStateManager.getCapabilities() == null ? null
108                : mStateManager.getCapabilities().toPublicCharacteristics();
109    }
110
111    @Override
112    public void connect(final IBinder binder, String callingPackage,
113            IWifiAwareEventCallback callback, ConfigRequest configRequest,
114            boolean notifyOnIdentityChanged) {
115        enforceAccessPermission();
116        enforceChangePermission();
117        if (callback == null) {
118            throw new IllegalArgumentException("Callback must not be null");
119        }
120        if (binder == null) {
121            throw new IllegalArgumentException("Binder must not be null");
122        }
123
124        if (notifyOnIdentityChanged) {
125            enforceLocationPermission();
126        }
127
128        if (configRequest != null) {
129            enforceConnectivityInternalPermission();
130        } else {
131            configRequest = new ConfigRequest.Builder().build();
132        }
133        configRequest.validate();
134
135        final int uid = getMockableCallingUid();
136        int pid = getCallingPid();
137
138        final int clientId;
139        synchronized (mLock) {
140            clientId = mNextClientId++;
141        }
142
143        if (VDBG) {
144            Log.v(TAG, "connect: uid=" + uid + ", clientId=" + clientId + ", configRequest"
145                    + configRequest + ", notifyOnIdentityChanged=" + notifyOnIdentityChanged);
146        }
147
148        IBinder.DeathRecipient dr = new IBinder.DeathRecipient() {
149            @Override
150            public void binderDied() {
151                if (DBG) Log.d(TAG, "binderDied: clientId=" + clientId);
152                binder.unlinkToDeath(this, 0);
153
154                synchronized (mLock) {
155                    mDeathRecipientsByClientId.delete(clientId);
156                    mUidByClientId.delete(clientId);
157                }
158
159                mStateManager.disconnect(clientId);
160            }
161        };
162
163        try {
164            binder.linkToDeath(dr, 0);
165        } catch (RemoteException e) {
166            Log.e(TAG, "Error on linkToDeath - " + e);
167            try {
168                callback.onConnectFail(NanStatusType.INTERNAL_FAILURE);
169            } catch (RemoteException e1) {
170                Log.e(TAG, "Error on onConnectFail()");
171            }
172            return;
173        }
174
175        synchronized (mLock) {
176            mDeathRecipientsByClientId.put(clientId, dr);
177            mUidByClientId.put(clientId, uid);
178        }
179
180        mStateManager.connect(clientId, uid, pid, callingPackage, callback, configRequest,
181                notifyOnIdentityChanged);
182    }
183
184    @Override
185    public void disconnect(int clientId, IBinder binder) {
186        enforceAccessPermission();
187        enforceChangePermission();
188
189        int uid = getMockableCallingUid();
190        enforceClientValidity(uid, clientId);
191        if (VDBG) Log.v(TAG, "disconnect: uid=" + uid + ", clientId=" + clientId);
192
193        if (binder == null) {
194            throw new IllegalArgumentException("Binder must not be null");
195        }
196
197        synchronized (mLock) {
198            IBinder.DeathRecipient dr = mDeathRecipientsByClientId.get(clientId);
199            if (dr != null) {
200                binder.unlinkToDeath(dr, 0);
201                mDeathRecipientsByClientId.delete(clientId);
202            }
203            mUidByClientId.delete(clientId);
204        }
205
206        mStateManager.disconnect(clientId);
207    }
208
209    @Override
210    public void terminateSession(int clientId, int sessionId) {
211        enforceAccessPermission();
212        enforceChangePermission();
213
214        int uid = getMockableCallingUid();
215        enforceClientValidity(uid, clientId);
216        if (VDBG) {
217            Log.v(TAG, "terminateSession: sessionId=" + sessionId + ", uid=" + uid + ", clientId="
218                    + clientId);
219        }
220
221        mStateManager.terminateSession(clientId, sessionId);
222    }
223
224    @Override
225    public void publish(int clientId, PublishConfig publishConfig,
226            IWifiAwareDiscoverySessionCallback callback) {
227        enforceAccessPermission();
228        enforceChangePermission();
229        enforceLocationPermission();
230
231        if (callback == null) {
232            throw new IllegalArgumentException("Callback must not be null");
233        }
234        if (publishConfig == null) {
235            throw new IllegalArgumentException("PublishConfig must not be null");
236        }
237        publishConfig.assertValid(mStateManager.getCharacteristics());
238
239        int uid = getMockableCallingUid();
240        enforceClientValidity(uid, clientId);
241        if (VDBG) {
242            Log.v(TAG, "publish: uid=" + uid + ", clientId=" + clientId + ", publishConfig="
243                    + publishConfig + ", callback=" + callback);
244        }
245
246        mStateManager.publish(clientId, publishConfig, callback);
247    }
248
249    @Override
250    public void updatePublish(int clientId, int sessionId, PublishConfig publishConfig) {
251        enforceAccessPermission();
252        enforceChangePermission();
253
254        if (publishConfig == null) {
255            throw new IllegalArgumentException("PublishConfig must not be null");
256        }
257        publishConfig.assertValid(mStateManager.getCharacteristics());
258
259        int uid = getMockableCallingUid();
260        enforceClientValidity(uid, clientId);
261        if (VDBG) {
262            Log.v(TAG, "updatePublish: uid=" + uid + ", clientId=" + clientId + ", sessionId="
263                    + sessionId + ", config=" + publishConfig);
264        }
265
266        mStateManager.updatePublish(clientId, sessionId, publishConfig);
267    }
268
269    @Override
270    public void subscribe(int clientId, SubscribeConfig subscribeConfig,
271            IWifiAwareDiscoverySessionCallback callback) {
272        enforceAccessPermission();
273        enforceChangePermission();
274        enforceLocationPermission();
275
276        if (callback == null) {
277            throw new IllegalArgumentException("Callback must not be null");
278        }
279        if (subscribeConfig == null) {
280            throw new IllegalArgumentException("SubscribeConfig must not be null");
281        }
282        subscribeConfig.assertValid(mStateManager.getCharacteristics());
283
284        int uid = getMockableCallingUid();
285        enforceClientValidity(uid, clientId);
286        if (VDBG) {
287            Log.v(TAG, "subscribe: uid=" + uid + ", clientId=" + clientId + ", config="
288                    + subscribeConfig + ", callback=" + callback);
289        }
290
291        mStateManager.subscribe(clientId, subscribeConfig, callback);
292    }
293
294    @Override
295    public void updateSubscribe(int clientId, int sessionId, SubscribeConfig subscribeConfig) {
296        enforceAccessPermission();
297        enforceChangePermission();
298
299        if (subscribeConfig == null) {
300            throw new IllegalArgumentException("SubscribeConfig must not be null");
301        }
302        subscribeConfig.assertValid(mStateManager.getCharacteristics());
303
304        int uid = getMockableCallingUid();
305        enforceClientValidity(uid, clientId);
306        if (VDBG) {
307            Log.v(TAG, "updateSubscribe: uid=" + uid + ", clientId=" + clientId + ", sessionId="
308                    + sessionId + ", config=" + subscribeConfig);
309        }
310
311        mStateManager.updateSubscribe(clientId, sessionId, subscribeConfig);
312    }
313
314    @Override
315    public void sendMessage(int clientId, int sessionId, int peerId, byte[] message, int messageId,
316            int retryCount) {
317        enforceAccessPermission();
318        enforceChangePermission();
319
320        if (retryCount != 0) {
321            enforceConnectivityInternalPermission();
322        }
323
324        if (message != null
325                && message.length > mStateManager.getCharacteristics().getMaxServiceNameLength()) {
326            throw new IllegalArgumentException(
327                    "Message length longer than supported by device characteristics");
328        }
329        if (retryCount < 0 || retryCount > DiscoverySession.getMaxSendRetryCount()) {
330            throw new IllegalArgumentException("Invalid 'retryCount' must be non-negative "
331                    + "and <= DiscoverySession.MAX_SEND_RETRY_COUNT");
332        }
333
334        int uid = getMockableCallingUid();
335        enforceClientValidity(uid, clientId);
336        if (VDBG) {
337            Log.v(TAG,
338                    "sendMessage: sessionId=" + sessionId + ", uid=" + uid + ", clientId="
339                            + clientId + ", peerId=" + peerId + ", messageId=" + messageId
340                            + ", retryCount=" + retryCount);
341        }
342
343        mStateManager.sendMessage(clientId, sessionId, peerId, message, messageId, retryCount);
344    }
345
346    @Override
347    public int startRanging(int clientId, int sessionId, RttManager.ParcelableRttParams params) {
348        enforceAccessPermission();
349        enforceLocationPermission();
350
351        // TODO: b/35676064 restricts access to this API until decide if will open.
352        enforceConnectivityInternalPermission();
353
354        int uid = getMockableCallingUid();
355        enforceClientValidity(uid, clientId);
356        if (VDBG) {
357            Log.v(TAG, "startRanging: clientId=" + clientId + ", sessionId=" + sessionId + ", "
358                    + ", parms=" + Arrays.toString(params.mParams));
359        }
360
361        if (params.mParams.length == 0) {
362            throw new IllegalArgumentException("Empty ranging parameters");
363        }
364
365        int rangingId;
366        synchronized (mLock) {
367            rangingId = mNextRangingId++;
368        }
369        mStateManager.startRanging(clientId, sessionId, params.mParams, rangingId);
370        return rangingId;
371    }
372
373    @Override
374    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
375        if (mContext.checkCallingOrSelfPermission(
376                android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) {
377            pw.println("Permission Denial: can't dump WifiAwareService from pid="
378                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
379            return;
380        }
381        pw.println("Wi-Fi Aware Service");
382        synchronized (mLock) {
383            pw.println("  mNextClientId: " + mNextClientId);
384            pw.println("  mDeathRecipientsByClientId: " + mDeathRecipientsByClientId);
385            pw.println("  mUidByClientId: " + mUidByClientId);
386        }
387        mStateManager.dump(fd, pw, args);
388    }
389
390    private void enforceClientValidity(int uid, int clientId) {
391        synchronized (mLock) {
392            int uidIndex = mUidByClientId.indexOfKey(clientId);
393            if (uidIndex < 0 || mUidByClientId.valueAt(uidIndex) != uid) {
394                throw new SecurityException("Attempting to use invalid uid+clientId mapping: uid="
395                        + uid + ", clientId=" + clientId);
396            }
397        }
398    }
399
400    private void enforceAccessPermission() {
401        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE, TAG);
402    }
403
404    private void enforceChangePermission() {
405        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE, TAG);
406    }
407
408    private void enforceLocationPermission() {
409        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION,
410                TAG);
411    }
412
413    private void enforceConnectivityInternalPermission() {
414        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL,
415                TAG);
416    }
417}
418