| /* |
| * Copyright (C) 2017 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.lowpan; |
| |
| import android.annotation.NonNull; |
| import android.content.Context; |
| import android.net.ConnectivityManager; |
| import android.net.IpPrefix; |
| import android.net.LinkAddress; |
| import android.net.LinkProperties; |
| import android.net.NetworkAgent; |
| import android.net.NetworkCapabilities; |
| import android.net.NetworkFactory; |
| import android.net.NetworkInfo; |
| import android.net.NetworkInfo.DetailedState; |
| import android.net.ip.IIpClient; |
| import android.net.ip.IpClientCallbacks; |
| import android.net.ip.IpClientUtil; |
| import android.net.lowpan.ILowpanInterface; |
| import android.net.lowpan.LowpanException; |
| import android.net.lowpan.LowpanInterface; |
| import android.net.lowpan.LowpanRuntimeException; |
| import android.net.shared.InitialConfiguration; |
| import android.net.shared.ProvisioningConfiguration; |
| import android.os.ConditionVariable; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.RemoteException; |
| import android.os.ServiceSpecificException; |
| import android.util.Log; |
| import com.android.internal.util.HexDump; |
| import com.android.internal.util.Protocol; |
| import com.android.internal.util.State; |
| import com.android.internal.util.StateMachine; |
| |
| /** Tracks connectivity of a LoWPAN interface. */ |
| class LowpanInterfaceTracker extends StateMachine { |
| |
| // Misc Constants |
| |
| /** Network type string for NetworkInfo */ |
| private static final String NETWORK_TYPE = "LoWPAN"; |
| |
| /** Tag used for logging */ |
| private static final String TAG = "LowpanInterfaceTracker"; |
| |
| /** |
| * Maximum network score for LoWPAN networks. |
| * |
| * <p>TODO: Research if 30 is an appropriate value. |
| */ |
| private static final int NETWORK_SCORE = 30; |
| |
| // TODO: create IpClient asynchronously |
| private static final int IPCLIENT_CREATE_TIMEOUT_MS = 10000; |
| |
| /** Internal debugging flag. */ |
| private static final boolean DBG = true; |
| |
| /** Number of state machine log records. */ |
| public static final short NUM_LOG_RECS_NORMAL = 100; |
| |
| // Message Code Enumeration Constants |
| |
| /** The base for LoWPAN message codes */ |
| static final int BASE = Protocol.BASE_LOWPAN; |
| |
| static final int CMD_START_NETWORK = BASE + 3; |
| static final int CMD_STOP_NETWORK = BASE + 4; |
| static final int CMD_STATE_CHANGE = BASE + 5; |
| static final int CMD_LINK_PROPERTIES_CHANGE = BASE + 6; |
| static final int CMD_UNWANTED = BASE + 7; |
| static final int CMD_PROVISIONING_SUCCESS = BASE + 8; |
| static final int CMD_PROVISIONING_FAILURE = BASE + 9; |
| |
| // Services and interfaces |
| |
| ILowpanInterface mILowpanInterface; |
| private LowpanInterface mLowpanInterface; |
| private NetworkAgent mNetworkAgent; |
| private NetworkFactory mNetworkFactory; |
| private volatile IIpClient mIpClient; |
| private final IpClientCallbackImpl mIpClientCallback = new IpClientCallbackImpl(); |
| |
| // Instance Variables |
| |
| private String mInterfaceName; |
| private String mHwAddr; |
| private Context mContext; |
| private NetworkInfo mNetworkInfo; |
| private LinkProperties mLinkProperties; |
| private final NetworkCapabilities mNetworkCapabilities = new NetworkCapabilities(); |
| private String mState = ""; |
| |
| // State machine state instances |
| |
| final DefaultState mDefaultState = new DefaultState(); |
| final NormalState mNormalState = new NormalState(); |
| final OfflineState mOfflineState = new OfflineState(); |
| final CommissioningState mCommissioningState = new CommissioningState(); |
| final AttachingState mAttachingState = new AttachingState(); |
| final AttachedState mAttachedState = new AttachedState(); |
| final ObtainingIpState mObtainingIpState = new ObtainingIpState(); |
| final FaultState mFaultState = new FaultState(); |
| final ConnectedState mConnectedState = new ConnectedState(); |
| |
| private LocalLowpanCallback mLocalLowpanCallback = new LocalLowpanCallback(); |
| |
| // Misc Private Classes |
| |
| private class LocalLowpanCallback extends LowpanInterface.Callback { |
| @Override |
| public void onEnabledChanged(boolean value) {} |
| |
| @Override |
| public void onUpChanged(boolean value) {} |
| |
| @Override |
| public void onConnectedChanged(boolean value) {} |
| |
| @Override |
| public void onStateChanged(@NonNull String state) { |
| LowpanInterfaceTracker.this.sendMessage(CMD_STATE_CHANGE, state); |
| } |
| } |
| |
| class IpClientCallbackImpl extends IpClientCallbacks { |
| private final ConditionVariable mObtainedCv = new ConditionVariable(false); |
| |
| @Override |
| public void onIpClientCreated(IIpClient ipClient) { |
| mIpClient = ipClient; |
| mObtainedCv.open(); |
| } |
| |
| public boolean waitUntilCreated() { |
| return mObtainedCv.block(IPCLIENT_CREATE_TIMEOUT_MS); |
| } |
| |
| @Override |
| public void onProvisioningSuccess(LinkProperties newLp) { |
| LowpanInterfaceTracker.this.sendMessage(CMD_PROVISIONING_SUCCESS, newLp); |
| } |
| |
| @Override |
| public void onProvisioningFailure(LinkProperties newLp) { |
| LowpanInterfaceTracker.this.sendMessage(CMD_PROVISIONING_FAILURE, newLp); |
| } |
| |
| @Override |
| public void onLinkPropertiesChange(LinkProperties newLp) { |
| LowpanInterfaceTracker.this.sendMessage(CMD_LINK_PROPERTIES_CHANGE, newLp); |
| } |
| } |
| |
| // State Definitions |
| |
| class DefaultState extends State { |
| @Override |
| public void enter() { |
| if (DBG) { |
| Log.i(TAG, "DefaultState.enter()"); |
| } |
| |
| mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_NONE, 0, NETWORK_TYPE, ""); |
| mNetworkInfo.setIsAvailable(true); |
| |
| mLowpanInterface.registerCallback(mLocalLowpanCallback); |
| |
| mState = ""; |
| |
| sendMessage(CMD_STATE_CHANGE, mLowpanInterface.getState()); |
| } |
| |
| @Override |
| public boolean processMessage(Message message) { |
| boolean retValue = NOT_HANDLED; |
| |
| switch (message.what) { |
| case CMD_START_NETWORK: |
| if (DBG) { |
| Log.i(TAG, "CMD_START_NETWORK"); |
| } |
| try { |
| mLowpanInterface.setEnabled(true); |
| } catch (LowpanException | LowpanRuntimeException x) { |
| Log.e(TAG, "Exception while enabling: " + x); |
| transitionTo(mFaultState); |
| return HANDLED; |
| } |
| break; |
| |
| case CMD_STOP_NETWORK: |
| if (DBG) { |
| Log.i(TAG, "CMD_STOP_NETWORK"); |
| } |
| try { |
| mLowpanInterface.setEnabled(false); |
| } catch (LowpanException | LowpanRuntimeException x) { |
| Log.e(TAG, "Exception while disabling: " + x); |
| transitionTo(mFaultState); |
| return HANDLED; |
| } |
| break; |
| |
| case CMD_STATE_CHANGE: |
| if (!mState.equals(message.obj)) { |
| if (DBG) { |
| Log.i( |
| TAG, |
| "LowpanInterface changed state from \"" |
| + mState |
| + "\" to \"" |
| + message.obj |
| + "\"."); |
| } |
| mState = (String) message.obj; |
| switch (mState) { |
| case LowpanInterface.STATE_OFFLINE: |
| transitionTo(mOfflineState); |
| break; |
| case LowpanInterface.STATE_COMMISSIONING: |
| transitionTo(mCommissioningState); |
| break; |
| case LowpanInterface.STATE_ATTACHING: |
| transitionTo(mAttachingState); |
| break; |
| case LowpanInterface.STATE_ATTACHED: |
| transitionTo(mObtainingIpState); |
| break; |
| case LowpanInterface.STATE_FAULT: |
| transitionTo(mFaultState); |
| break; |
| } |
| } |
| retValue = HANDLED; |
| break; |
| } |
| return retValue; |
| } |
| |
| @Override |
| public void exit() { |
| mLowpanInterface.unregisterCallback(mLocalLowpanCallback); |
| } |
| } |
| |
| class NormalState extends State { |
| @Override |
| public void enter() { |
| if (DBG) { |
| Log.i(TAG, "NormalState.enter()"); |
| } |
| |
| IpClientUtil.makeIpClient(mContext, mInterfaceName, mIpClientCallback); |
| if (!mIpClientCallback.waitUntilCreated()) { |
| Log.e(TAG, "Failed to create IpClient"); |
| transitionTo(mFaultState); |
| return; |
| } |
| |
| if (mHwAddr == null) { |
| byte[] hwAddr = null; |
| try { |
| hwAddr = mLowpanInterface.getService().getMacAddress(); |
| |
| } catch (RemoteException | ServiceSpecificException x) { |
| // Don't let misbehavior of the interface service |
| // crash the system service. |
| Log.e(TAG, "Call to getMacAddress() failed: " + x); |
| transitionTo(mFaultState); |
| } |
| |
| if (hwAddr != null) { |
| mHwAddr = HexDump.toHexString(hwAddr); |
| } |
| } |
| |
| mNetworkFactory.register(); |
| } |
| |
| @Override |
| public boolean processMessage(Message message) { |
| switch (message.what) { |
| case CMD_UNWANTED: |
| if (mNetworkAgent == message.obj) { |
| if (DBG) { |
| Log.i(TAG, "UNWANTED."); |
| } |
| |
| try { |
| mLowpanInterface.setEnabled(false); |
| } catch (LowpanException | LowpanRuntimeException x) { |
| Log.e(TAG, "Exception while disabling: " + x); |
| transitionTo(mFaultState); |
| return HANDLED; |
| } |
| |
| shutdownNetworkAgent(); |
| } |
| break; |
| |
| case CMD_LINK_PROPERTIES_CHANGE: |
| mLinkProperties = (LinkProperties) message.obj; |
| if (DBG) { |
| Log.i(TAG, "Got LinkProperties: " + mLinkProperties); |
| } |
| if (mNetworkAgent != null) { |
| mNetworkAgent.sendLinkProperties(mLinkProperties); |
| } |
| break; |
| |
| case CMD_PROVISIONING_FAILURE: |
| Log.i(TAG, "Provisioning Failure: " + message.obj); |
| break; |
| } |
| |
| return NOT_HANDLED; |
| } |
| |
| @Override |
| public void exit() { |
| shutdownNetworkAgent(); |
| mNetworkFactory.unregister(); |
| |
| if (mIpClient != null) { |
| try { |
| mIpClient.shutdown(); |
| } catch (RemoteException e) { |
| Log.e(TAG, "Error shutting down IpClient", e); |
| } |
| } |
| mIpClient = null; |
| } |
| } |
| |
| class OfflineState extends State { |
| @Override |
| public void enter() { |
| shutdownNetworkAgent(); |
| mNetworkInfo.setIsAvailable(true); |
| |
| try { |
| mIpClient.stop(); |
| } catch (RemoteException e) { |
| Log.e(TAG, "Error stopping IpClient"); |
| } |
| } |
| |
| @Override |
| public boolean processMessage(Message message) { |
| return NOT_HANDLED; |
| } |
| |
| @Override |
| public void exit() {} |
| } |
| |
| class CommissioningState extends State { |
| @Override |
| public void enter() {} |
| |
| @Override |
| public boolean processMessage(Message message) { |
| return NOT_HANDLED; |
| } |
| |
| @Override |
| public void exit() {} |
| } |
| |
| class AttachingState extends State { |
| @Override |
| public void enter() { |
| mNetworkInfo.setDetailedState(DetailedState.CONNECTING, null, mHwAddr); |
| mNetworkInfo.setIsAvailable(true); |
| bringUpNetworkAgent(); |
| mNetworkAgent.sendNetworkInfo(mNetworkInfo); |
| } |
| |
| @Override |
| public boolean processMessage(Message message) { |
| return NOT_HANDLED; |
| } |
| |
| @Override |
| public void exit() {} |
| } |
| |
| class AttachedState extends State { |
| @Override |
| public void enter() { |
| bringUpNetworkAgent(); |
| mNetworkInfo.setIsAvailable(true); |
| } |
| |
| @Override |
| public boolean processMessage(Message message) { |
| switch (message.what) { |
| case CMD_STATE_CHANGE: |
| if (!mState.equals(message.obj) |
| && !LowpanInterface.STATE_ATTACHED.equals(message.obj)) { |
| return NOT_HANDLED; |
| } |
| return HANDLED; |
| |
| default: |
| return NOT_HANDLED; |
| } |
| } |
| |
| @Override |
| public void exit() { |
| mNetworkInfo.setIsAvailable(false); |
| } |
| } |
| |
| class ObtainingIpState extends State { |
| @Override |
| public void enter() { |
| InitialConfiguration initialConfiguration = new InitialConfiguration(); |
| |
| try { |
| for (LinkAddress address : mLowpanInterface.getLinkAddresses()) { |
| if (DBG) { |
| Log.i(TAG, "Adding link address: " + address); |
| } |
| |
| initialConfiguration.ipAddresses.add(address); |
| |
| IpPrefix prefix = new IpPrefix(address.getAddress(), address.getPrefixLength()); |
| |
| initialConfiguration.directlyConnectedRoutes.add(prefix); |
| } |
| |
| for (IpPrefix prefix : mLowpanInterface.getLinkNetworks()) { |
| if (DBG) { |
| Log.i(TAG, "Adding directly connected route: " + prefix); |
| } |
| |
| initialConfiguration.directlyConnectedRoutes.add(prefix); |
| } |
| |
| } catch (LowpanException | LowpanRuntimeException x) { |
| Log.e(TAG, "Exception while populating InitialConfiguration: " + x); |
| transitionTo(mFaultState); |
| return; |
| |
| } catch (RuntimeException x) { |
| if (x.getCause() instanceof RemoteException) { |
| // Don't let misbehavior of an interface service |
| // crash the system service. |
| Log.e(TAG, "RuntimeException while populating InitialConfiguration: " + x); |
| transitionTo(mFaultState); |
| |
| } else { |
| // This exception wasn't remote in origin, so we rethrow. |
| throw x; |
| } |
| } |
| |
| if (!initialConfiguration.isValid()) { |
| Log.e(TAG, "Invalid initial configuration: " + initialConfiguration); |
| transitionTo(mFaultState); |
| return; |
| } |
| |
| if (DBG) { |
| Log.d(TAG, "Using Initial configuration: " + initialConfiguration); |
| } |
| |
| final ProvisioningConfiguration.Builder builder = |
| new ProvisioningConfiguration.Builder(); |
| |
| builder.withInitialConfiguration(initialConfiguration).withProvisioningTimeoutMs(0); |
| |
| // LoWPAN networks generally don't have internet connectivity, |
| // so the reachability monitor would almost always fail. |
| builder.withoutIpReachabilityMonitor(); |
| |
| // We currently only support IPv6 on LoWPAN networks, although |
| // theoretically we could make this determination by examining |
| // the InitialConfiguration for any IPv4 addresses. |
| builder.withoutIPv4(); |
| |
| try { |
| mIpClient.startProvisioning(builder.build().toStableParcelable()); |
| } catch (RemoteException e) { |
| Log.e(TAG, "Error starting IpClient provisioning", e); |
| transitionTo(mFaultState); |
| return; |
| } |
| |
| mNetworkInfo.setDetailedState(DetailedState.OBTAINING_IPADDR, null, mHwAddr); |
| mNetworkAgent.sendNetworkInfo(mNetworkInfo); |
| } |
| |
| @Override |
| public boolean processMessage(Message message) { |
| |
| switch (message.what) { |
| case CMD_PROVISIONING_SUCCESS: |
| Log.i(TAG, "Provisioning Success: " + message.obj); |
| transitionTo(mConnectedState); |
| return HANDLED; |
| } |
| return NOT_HANDLED; |
| } |
| |
| @Override |
| public void exit() {} |
| } |
| |
| class ConnectedState extends State { |
| @Override |
| public void enter() { |
| mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, mHwAddr); |
| mNetworkAgent.sendNetworkInfo(mNetworkInfo); |
| mNetworkAgent.sendNetworkScore(NETWORK_SCORE); |
| } |
| |
| @Override |
| public boolean processMessage(Message message) { |
| return NOT_HANDLED; |
| } |
| |
| @Override |
| public void exit() { |
| if (mNetworkAgent != null) { |
| mNetworkAgent.sendNetworkScore(0); |
| } |
| } |
| } |
| |
| class FaultState extends State { |
| @Override |
| public void enter() {} |
| |
| @Override |
| public boolean processMessage(Message message) { |
| return NOT_HANDLED; |
| } |
| |
| @Override |
| public void exit() {} |
| } |
| |
| public LowpanInterfaceTracker(Context context, ILowpanInterface ifaceService, Looper looper) { |
| super(TAG, looper); |
| |
| if (DBG) { |
| Log.i(TAG, "LowpanInterfaceTracker() begin"); |
| } |
| |
| setDbg(DBG); |
| setLogRecSize(NUM_LOG_RECS_NORMAL); |
| setLogOnlyTransitions(false); |
| |
| mILowpanInterface = ifaceService; |
| mLowpanInterface = new LowpanInterface(context, ifaceService, looper); |
| mContext = context; |
| |
| mInterfaceName = mLowpanInterface.getName(); |
| |
| mLinkProperties = new LinkProperties(); |
| mLinkProperties.setInterfaceName(mInterfaceName); |
| |
| // Initialize capabilities |
| mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_LOWPAN); |
| mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED); |
| mNetworkCapabilities.setLinkUpstreamBandwidthKbps(100); |
| mNetworkCapabilities.setLinkDownstreamBandwidthKbps(100); |
| |
| // CHECKSTYLE:OFF IndentationCheck |
| addState(mDefaultState); |
| addState(mFaultState, mDefaultState); |
| addState(mNormalState, mDefaultState); |
| addState(mOfflineState, mNormalState); |
| addState(mCommissioningState, mNormalState); |
| addState(mAttachingState, mNormalState); |
| addState(mAttachedState, mNormalState); |
| addState(mObtainingIpState, mAttachedState); |
| addState(mConnectedState, mAttachedState); |
| // CHECKSTYLE:ON IndentationCheck |
| |
| setInitialState(mDefaultState); |
| |
| mNetworkFactory = |
| new NetworkFactory(looper, context, NETWORK_TYPE, mNetworkCapabilities) { |
| @Override |
| protected void startNetwork() { |
| LowpanInterfaceTracker.this.sendMessage(CMD_START_NETWORK); |
| } |
| |
| @Override |
| protected void stopNetwork() { |
| LowpanInterfaceTracker.this.sendMessage(CMD_STOP_NETWORK); |
| } |
| }; |
| |
| if (DBG) { |
| Log.i(TAG, "LowpanInterfaceTracker() end"); |
| } |
| } |
| |
| private void shutdownNetworkAgent() { |
| mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, mHwAddr); |
| mNetworkInfo.setIsAvailable(false); |
| |
| if (mNetworkAgent != null) { |
| mNetworkAgent.sendNetworkScore(0); |
| mNetworkAgent.sendNetworkInfo(mNetworkInfo); |
| } |
| |
| mNetworkAgent = null; |
| } |
| |
| private void bringUpNetworkAgent() { |
| if (mNetworkAgent == null) { |
| mNetworkAgent = |
| new NetworkAgent( |
| mNetworkFactory.getLooper(), |
| mContext, |
| NETWORK_TYPE, |
| mNetworkInfo, |
| mNetworkCapabilities, |
| mLinkProperties, |
| NETWORK_SCORE) { |
| public void unwanted() { |
| LowpanInterfaceTracker.this.sendMessage(CMD_UNWANTED, this); |
| }; |
| }; |
| } |
| } |
| |
| public void register() { |
| if (DBG) { |
| Log.i(TAG, "register()"); |
| } |
| start(); |
| } |
| |
| public void unregister() { |
| if (DBG) { |
| Log.i(TAG, "unregister()"); |
| } |
| quit(); |
| } |
| } |