/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.wifi.aware; import static com.android.server.wifi.WifiSettingsConfigStore.WIFI_VERBOSE_LOGGING_ENABLED; import android.Manifest; import android.annotation.NonNull; import android.app.AppOpsManager; import android.content.Context; import android.content.pm.PackageManager; import android.hardware.wifi.V1_0.NanStatusType; import android.net.wifi.aware.AwareResources; import android.net.wifi.aware.Characteristics; import android.net.wifi.aware.ConfigRequest; import android.net.wifi.aware.DiscoverySession; import android.net.wifi.aware.IWifiAwareDiscoverySessionCallback; import android.net.wifi.aware.IWifiAwareEventCallback; import android.net.wifi.aware.IWifiAwareMacAddressProvider; import android.net.wifi.aware.IWifiAwareManager; import android.net.wifi.aware.PublishConfig; import android.net.wifi.aware.SubscribeConfig; import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.util.Log; import android.util.SparseArray; import android.util.SparseIntArray; import com.android.server.wifi.Clock; import com.android.server.wifi.WifiSettingsConfigStore; import com.android.server.wifi.util.NetdWrapper; import com.android.server.wifi.util.WifiPermissionsUtil; import com.android.server.wifi.util.WifiPermissionsWrapper; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.List; /** * Implementation of the IWifiAwareManager AIDL interface. Performs validity * (permission and clientID-UID mapping) checks and delegates execution to the * WifiAwareStateManager singleton handler. Limited state to feedback which has to * be provided instantly: client and session IDs. */ public class WifiAwareServiceImpl extends IWifiAwareManager.Stub { private static final String TAG = "WifiAwareService"; private boolean mDbg = false; private Context mContext; private AppOpsManager mAppOps; private WifiPermissionsUtil mWifiPermissionsUtil; private WifiAwareStateManager mStateManager; private WifiAwareShellCommand mShellCommand; private Handler mHandler; private final Object mLock = new Object(); private final SparseArray mDeathRecipientsByClientId = new SparseArray<>(); private int mNextClientId = 1; private final SparseIntArray mUidByClientId = new SparseIntArray(); public WifiAwareServiceImpl(Context context) { mContext = context; mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); } /** * Proxy for the final native call of the parent class. Enables mocking of * the function. */ public int getMockableCallingUid() { return getCallingUid(); } /** * Start the service: allocate a new thread (for now), start the handlers of * the components of the service. */ public void start(HandlerThread handlerThread, WifiAwareStateManager awareStateManager, WifiAwareShellCommand awareShellCommand, WifiAwareMetrics awareMetrics, WifiPermissionsUtil wifiPermissionsUtil, WifiPermissionsWrapper permissionsWrapper, WifiSettingsConfigStore settingsConfigStore, WifiAwareNativeManager wifiAwareNativeManager, WifiAwareNativeApi wifiAwareNativeApi, WifiAwareNativeCallback wifiAwareNativeCallback, NetdWrapper netdWrapper) { Log.i(TAG, "Starting Wi-Fi Aware service"); mWifiPermissionsUtil = wifiPermissionsUtil; mStateManager = awareStateManager; mShellCommand = awareShellCommand; mHandler = new Handler(handlerThread.getLooper()); mHandler.post(() -> { mStateManager.start(mContext, handlerThread.getLooper(), awareMetrics, wifiPermissionsUtil, permissionsWrapper, new Clock(), netdWrapper); settingsConfigStore.registerChangeListener( WIFI_VERBOSE_LOGGING_ENABLED, (key, newValue) -> enableVerboseLogging(newValue, awareStateManager, wifiAwareNativeManager, wifiAwareNativeApi, wifiAwareNativeCallback), mHandler); enableVerboseLogging(settingsConfigStore.get(WIFI_VERBOSE_LOGGING_ENABLED), awareStateManager, wifiAwareNativeManager, wifiAwareNativeApi, wifiAwareNativeCallback); }); } private void enableVerboseLogging(boolean dbg, WifiAwareStateManager awareStateManager, WifiAwareNativeManager wifiAwareNativeManager, WifiAwareNativeApi wifiAwareNativeApi, WifiAwareNativeCallback wifiAwareNativeCallback) { mDbg = dbg; awareStateManager.enableVerboseLogging(dbg); if (awareStateManager.mDataPathMgr != null) { // needed for unit tests awareStateManager.mDataPathMgr.enableVerboseLogging(dbg); } wifiAwareNativeCallback.enableVerboseLogging(dbg); wifiAwareNativeManager.enableVerboseLogging(dbg); wifiAwareNativeApi.enableVerboseLogging(dbg); } /** * Start/initialize portions of the service which require the boot stage to be complete. */ public void startLate() { Log.i(TAG, "Late initialization of Wi-Fi Aware service"); mHandler.post(() -> mStateManager.startLate()); } @Override public boolean isUsageEnabled() { enforceAccessPermission(); return mStateManager.isUsageEnabled(); } @Override public Characteristics getCharacteristics() { enforceAccessPermission(); return mStateManager.getCapabilities() == null ? null : mStateManager.getCapabilities().toPublicCharacteristics(); } @Override public AwareResources getAvailableAwareResources() { enforceAccessPermission(); return mStateManager.getAvailableAwareResources(); } @Override public boolean isDeviceAttached() { enforceAccessPermission(); return mDeathRecipientsByClientId.size() != 0; } @Override public void enableInstantCommunicationMode(String callingPackage, boolean enable) { if (!mWifiPermissionsUtil.isSystem(callingPackage, Binder.getCallingUid())) { Log.i(TAG, "enableInstantCommunicationMode not allowed for uid=" + Binder.getCallingUid()); return; } enforceChangePermission(); mStateManager.enableInstantCommunicationMode(enable); } @Override public boolean isInstantCommunicationModeEnabled() { enforceAccessPermission(); return mStateManager.isInstantCommunicationModeEnabled(); } @Override public void connect(final IBinder binder, String callingPackage, String callingFeatureId, IWifiAwareEventCallback callback, ConfigRequest configRequest, boolean notifyOnIdentityChanged) { enforceAccessPermission(); enforceChangePermission(); final int uid = getMockableCallingUid(); mAppOps.checkPackage(uid, callingPackage); if (callback == null) { throw new IllegalArgumentException("Callback must not be null"); } if (binder == null) { throw new IllegalArgumentException("Binder must not be null"); } if (notifyOnIdentityChanged) { enforceLocationPermission(callingPackage, callingFeatureId, getMockableCallingUid()); } if (configRequest != null) { enforceNetworkStackPermission(); } else { configRequest = new ConfigRequest.Builder().build(); } configRequest.validate(); int pid = getCallingPid(); final int clientId; synchronized (mLock) { clientId = mNextClientId++; } if (mDbg) { Log.v(TAG, "connect: uid=" + uid + ", clientId=" + clientId + ", configRequest" + configRequest + ", notifyOnIdentityChanged=" + notifyOnIdentityChanged); } IBinder.DeathRecipient dr = new IBinder.DeathRecipient() { @Override public void binderDied() { if (mDbg) Log.v(TAG, "binderDied: clientId=" + clientId); binder.unlinkToDeath(this, 0); synchronized (mLock) { mDeathRecipientsByClientId.delete(clientId); mUidByClientId.delete(clientId); } mStateManager.disconnect(clientId); } }; try { binder.linkToDeath(dr, 0); } catch (RemoteException e) { Log.e(TAG, "Error on linkToDeath - " + e); try { callback.onConnectFail(NanStatusType.INTERNAL_FAILURE); } catch (RemoteException e1) { Log.e(TAG, "Error on onConnectFail()"); } return; } synchronized (mLock) { mDeathRecipientsByClientId.put(clientId, dr); mUidByClientId.put(clientId, uid); } mStateManager.connect(clientId, uid, pid, callingPackage, callingFeatureId, callback, configRequest, notifyOnIdentityChanged); } @Override public void disconnect(int clientId, IBinder binder) { enforceAccessPermission(); enforceChangePermission(); int uid = getMockableCallingUid(); enforceClientValidity(uid, clientId); if (mDbg) Log.v(TAG, "disconnect: uid=" + uid + ", clientId=" + clientId); if (binder == null) { throw new IllegalArgumentException("Binder must not be null"); } synchronized (mLock) { IBinder.DeathRecipient dr = mDeathRecipientsByClientId.get(clientId); if (dr != null) { binder.unlinkToDeath(dr, 0); mDeathRecipientsByClientId.delete(clientId); } mUidByClientId.delete(clientId); } mStateManager.disconnect(clientId); } @Override public void terminateSession(int clientId, int sessionId) { enforceAccessPermission(); enforceChangePermission(); int uid = getMockableCallingUid(); enforceClientValidity(uid, clientId); if (mDbg) { Log.v(TAG, "terminateSession: sessionId=" + sessionId + ", uid=" + uid + ", clientId=" + clientId); } mStateManager.terminateSession(clientId, sessionId); } @Override public void publish(String callingPackage, String callingFeatureId, int clientId, PublishConfig publishConfig, IWifiAwareDiscoverySessionCallback callback) { enforceAccessPermission(); enforceChangePermission(); int uid = getMockableCallingUid(); mAppOps.checkPackage(uid, callingPackage); enforceLocationPermission(callingPackage, callingFeatureId, getMockableCallingUid()); if (callback == null) { throw new IllegalArgumentException("Callback must not be null"); } if (publishConfig == null) { throw new IllegalArgumentException("PublishConfig must not be null"); } publishConfig.assertValid(mStateManager.getCharacteristics(), mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_RTT)); enforceClientValidity(uid, clientId); if (mDbg) { Log.v(TAG, "publish: uid=" + uid + ", clientId=" + clientId + ", publishConfig=" + publishConfig + ", callback=" + callback); } mStateManager.publish(clientId, publishConfig, callback); } @Override public void updatePublish(int clientId, int sessionId, PublishConfig publishConfig) { enforceAccessPermission(); enforceChangePermission(); if (publishConfig == null) { throw new IllegalArgumentException("PublishConfig must not be null"); } publishConfig.assertValid(mStateManager.getCharacteristics(), mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_RTT)); int uid = getMockableCallingUid(); enforceClientValidity(uid, clientId); if (mDbg) { Log.v(TAG, "updatePublish: uid=" + uid + ", clientId=" + clientId + ", sessionId=" + sessionId + ", config=" + publishConfig); } mStateManager.updatePublish(clientId, sessionId, publishConfig); } @Override public void subscribe(String callingPackage, String callingFeatureId, int clientId, SubscribeConfig subscribeConfig, IWifiAwareDiscoverySessionCallback callback) { enforceAccessPermission(); enforceChangePermission(); int uid = getMockableCallingUid(); mAppOps.checkPackage(uid, callingPackage); enforceLocationPermission(callingPackage, callingFeatureId, getMockableCallingUid()); if (callback == null) { throw new IllegalArgumentException("Callback must not be null"); } if (subscribeConfig == null) { throw new IllegalArgumentException("SubscribeConfig must not be null"); } subscribeConfig.assertValid(mStateManager.getCharacteristics(), mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_RTT)); enforceClientValidity(uid, clientId); if (mDbg) { Log.v(TAG, "subscribe: uid=" + uid + ", clientId=" + clientId + ", config=" + subscribeConfig + ", callback=" + callback); } mStateManager.subscribe(clientId, subscribeConfig, callback); } @Override public void updateSubscribe(int clientId, int sessionId, SubscribeConfig subscribeConfig) { enforceAccessPermission(); enforceChangePermission(); if (subscribeConfig == null) { throw new IllegalArgumentException("SubscribeConfig must not be null"); } subscribeConfig.assertValid(mStateManager.getCharacteristics(), mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_RTT)); int uid = getMockableCallingUid(); enforceClientValidity(uid, clientId); if (mDbg) { Log.v(TAG, "updateSubscribe: uid=" + uid + ", clientId=" + clientId + ", sessionId=" + sessionId + ", config=" + subscribeConfig); } mStateManager.updateSubscribe(clientId, sessionId, subscribeConfig); } @Override public void sendMessage(int clientId, int sessionId, int peerId, byte[] message, int messageId, int retryCount) { enforceAccessPermission(); enforceChangePermission(); if (retryCount != 0) { enforceNetworkStackPermission(); } if (message != null && message.length > mStateManager.getCharacteristics().getMaxServiceSpecificInfoLength()) { throw new IllegalArgumentException( "Message length longer than supported by device characteristics"); } if (retryCount < 0 || retryCount > DiscoverySession.getMaxSendRetryCount()) { throw new IllegalArgumentException("Invalid 'retryCount' must be non-negative " + "and <= DiscoverySession.MAX_SEND_RETRY_COUNT"); } int uid = getMockableCallingUid(); enforceClientValidity(uid, clientId); if (mDbg) { Log.v(TAG, "sendMessage: sessionId=" + sessionId + ", uid=" + uid + ", clientId=" + clientId + ", peerId=" + peerId + ", messageId=" + messageId + ", retryCount=" + retryCount); } mStateManager.sendMessage(uid, clientId, sessionId, peerId, message, messageId, retryCount); } @Override public void requestMacAddresses(int uid, List peerIds, IWifiAwareMacAddressProvider callback) { enforceNetworkStackPermission(); mStateManager.requestMacAddresses(uid, peerIds, callback); } @Override public int handleShellCommand(@NonNull ParcelFileDescriptor in, @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err, @NonNull String[] args) { return mShellCommand.exec( this, in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), args); } @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission( android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { pw.println("Permission Denial: can't dump WifiAwareService from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); return; } pw.println("Wi-Fi Aware Service"); synchronized (mLock) { pw.println(" mNextClientId: " + mNextClientId); pw.println(" mDeathRecipientsByClientId: " + mDeathRecipientsByClientId); pw.println(" mUidByClientId: " + mUidByClientId); } mStateManager.dump(fd, pw, args); } private void enforceClientValidity(int uid, int clientId) { synchronized (mLock) { int uidIndex = mUidByClientId.indexOfKey(clientId); if (uidIndex < 0 || mUidByClientId.valueAt(uidIndex) != uid) { throw new SecurityException("Attempting to use invalid uid+clientId mapping: uid=" + uid + ", clientId=" + clientId); } } } private void enforceAccessPermission() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE, TAG); } private void enforceChangePermission() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE, TAG); } private void enforceLocationPermission(String callingPackage, String callingFeatureId, int uid) { mWifiPermissionsUtil.enforceLocationPermission(callingPackage, callingFeatureId, uid); } private void enforceNetworkStackPermission() { mContext.enforceCallingOrSelfPermission(Manifest.permission.NETWORK_STACK, TAG); } }