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