blob: cd548190fa178ae27b0997766153f5ae431eb6f1 [file] [log] [blame]
/*
* 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 android.net.lowpan;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.IpPrefix;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.util.Log;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* Class for managing a specific Low-power Wireless Personal Area Network (LoWPAN) interface.
*
* @hide
*/
//@SystemApi
public class LowpanInterface {
private static final String TAG = LowpanInterface.class.getSimpleName();
/** Detached role. The interface is not currently attached to a network. */
public static final String ROLE_DETACHED = "detached";
/** End-device role. End devices do not route traffic for other nodes. */
public static final String ROLE_END_DEVICE = "end-device";
/** Router role. Routers help route traffic around the mesh network. */
public static final String ROLE_ROUTER = "router";
/**
* Sleepy End-Device role.
*
* <p>End devices with this role are nominally asleep, waking up periodically to check in with
* their parent to see if there are packets destined for them. Such devices are capable of
* extraordinarilly low power consumption, but packet latency can be on the order of dozens of
* seconds(depending on how the node is configured).
*/
public static final String ROLE_SLEEPY_END_DEVICE = "sleepy-end-device";
/**
* Sleepy-router role.
*
* <p>Routers with this role are nominally asleep, waking up periodically to check in with other
* routers and their children.
*/
public static final String ROLE_SLEEPY_ROUTER = "sleepy-router";
/** TODO: doc */
public static final String ROLE_LEADER = "leader";
/** TODO: doc */
public static final String ROLE_COORDINATOR = "coordinator";
/**
* Offline state.
*
* <p>This is the initial state of the LoWPAN interface when the underlying driver starts. In
* this state the NCP is idle and not connected to any network.
*
* <p>This state can be explicitly entered by calling {@link #reset()}, {@link #leave()}, or
* <code>setUp(false)</code>, with the later two only working if we were not previously in the
* {@link #STATE_FAULT} state.
*
* @see #getState()
* @see #STATE_FAULT
*/
public static final String STATE_OFFLINE = "offline";
/**
* Commissioning state.
*
* <p>The interface enters this state after a call to {@link #startCommissioningSession()}. This
* state may only be entered directly from the {@link #STATE_OFFLINE} state.
*
* @see #startCommissioningSession()
* @see #getState()
* @hide
*/
public static final String STATE_COMMISSIONING = "commissioning";
/**
* Attaching state.
*
* <p>The interface enters this state when it starts the process of trying to find other nodes
* so that it can attach to any pre-existing network fragment, or when it is in the process of
* calculating the optimal values for unspecified parameters when forming a new network.
*
* <p>The interface may stay in this state for a prolonged period of time (or may spontaneously
* enter this state from {@link #STATE_ATTACHED}) if the underlying network technology is
* heirarchical (like ZigBeeIP) or if the device role is that of an "end-device" ({@link
* #ROLE_END_DEVICE} or {@link #ROLE_SLEEPY_END_DEVICE}). This is because such roles cannot
* create their own network fragments.
*
* @see #STATE_ATTACHED
* @see #getState()
*/
public static final String STATE_ATTACHING = "attaching";
/**
* Attached state.
*
* <p>The interface enters this state from {@link #STATE_ATTACHING} once it is actively
* participating on a network fragment.
*
* @see #STATE_ATTACHING
* @see #getState()
*/
public static final String STATE_ATTACHED = "attached";
/**
* Fault state.
*
* <p>The interface will enter this state when the driver has detected some sort of problem from
* which it was not immediately able to recover.
*
* <p>This state can be entered spontaneously from any other state. Calling {@link #reset} will
* cause the device to return to the {@link #STATE_OFFLINE} state.
*
* @see #getState
* @see #STATE_OFFLINE
*/
public static final String STATE_FAULT = "fault";
/**
* Network type for Thread 1.x networks.
*
* @see android.net.lowpan.LowpanIdentity#getType
* @see #getLowpanIdentity
* @hide
*/
public static final String NETWORK_TYPE_THREAD = "org.threadgroup.thread.v1";
/**
* Network type for ZigBeeIP 1.x networks.
*
* @see android.net.lowpan.LowpanIdentity#getType
* @see #getLowpanIdentity
* @hide
*/
public static final String NETWORK_TYPE_ZIGBEE_IP = "org.zigbee.zigbeeip.v1";
private static final String NETWORK_PROPERTY_KEYS[] = {
LowpanProperties.KEY_NETWORK_NAME.getName(),
LowpanProperties.KEY_NETWORK_PANID.getName(),
LowpanProperties.KEY_NETWORK_XPANID.getName(),
LowpanProperties.KEY_CHANNEL.getName()
};
/**
* Callback base class for LowpanInterface
*
* @hide
*/
//@SystemApi
public abstract static class Callback {
public void onConnectedChanged(boolean value) {}
public void onEnabledChanged(boolean value) {}
public void onUpChanged(boolean value) {}
public void onRoleChanged(@NonNull String value) {}
public void onStateChanged(@NonNull String state) {}
public void onLowpanIdentityChanged(@NonNull LowpanIdentity value) {}
/** @hide */
public void onPropertiesChanged(@NonNull Map properties) {}
}
private ILowpanInterface mBinder;
private final HashMap<Integer, ILowpanInterfaceListener> mListenerMap = new HashMap<>();
/** Map between IBinder identity hashes and LowpanInstance objects. */
private static final HashMap<Integer, LowpanInterface> sInstanceMap = new HashMap<>();
private LowpanInterface(IBinder binder) {
mBinder = ILowpanInterface.Stub.asInterface(binder);
}
/**
* Get the LowpanInterface object associated with this IBinder. Returns null if this IBinder
* does not implement the appropriate interface.
*
* @hide
*/
@NonNull
public static final LowpanInterface from(IBinder binder) {
Integer hashCode = Integer.valueOf(System.identityHashCode(binder));
LowpanInterface instance;
synchronized (sInstanceMap) {
instance = sInstanceMap.get(hashCode);
if (instance == null) {
instance = new LowpanInterface(binder);
sInstanceMap.put(hashCode, instance);
}
}
return instance;
}
/** {@hide} */
public static final LowpanInterface from(ILowpanInterface iface) {
return from(iface.asBinder());
}
/** {@hide} */
public static final LowpanInterface getInterfaceFromBinder(IBinder binder) {
return from(binder);
}
/**
* Returns the IBinder object associated with this interface.
*
* @hide
*/
public IBinder getBinder() {
return mBinder.asBinder();
}
private static void throwAsPublicException(Throwable t) throws LowpanException {
LowpanException.throwAsPublicException(t);
}
//////////////////////////////////////////////////////////////////////////
// Private Property Helpers
void setProperties(Map properties) throws LowpanException {
try {
mBinder.setProperties(properties);
} catch (RemoteException x) {
// Catch and ignore all binder exceptions
Log.e(TAG, x.toString());
} catch (ServiceSpecificException x) {
throwAsPublicException(x);
}
}
@NonNull
Map<String, Object> getProperties(String keys[]) throws LowpanException {
try {
return mBinder.getProperties(keys);
} catch (RemoteException x) {
// Catch and ignore all binder exceptions
Log.e(TAG, x.toString());
} catch (ServiceSpecificException x) {
throwAsPublicException(x);
}
return new HashMap();
}
/** @hide */
public <T> void setProperty(LowpanProperty<T> key, T value) throws LowpanException {
HashMap<String, T> prop = new HashMap<>();
prop.put(key.getName(), value);
setProperties(prop);
}
/** @hide */
@Nullable
public <T> T getProperty(LowpanProperty<T> key) throws LowpanException {
Map<String, Object> map = getProperties(new String[] {key.getName()});
if (map != null && !map.isEmpty()) {
// We know there is only one value.
return (T) map.values().iterator().next();
}
return null;
}
@Nullable
<T> String getPropertyAsString(LowpanProperty<T> key) throws LowpanException {
try {
return mBinder.getPropertyAsString(key.getName());
} catch (RemoteException x) {
// Catch and ignore all binder exceptions
Log.e(TAG, x.toString());
} catch (ServiceSpecificException x) {
throwAsPublicException(x);
}
return null;
}
int getPropertyAsInt(LowpanProperty<Integer> key) throws LowpanException {
Integer value = getProperty(key);
return (value != null) ? value : 0;
}
boolean getPropertyAsBoolean(LowpanProperty<Boolean> key) throws LowpanException {
Boolean value = getProperty(key);
return (value != null) ? value : 0;
}
//////////////////////////////////////////////////////////////////////////
// Public Actions
/**
* Form a new network with the given network information optional credential. Unspecified fields
* in the network information will be filled in with reasonable values. If the network
* credential is unspecified, one will be generated automatically.
*
* <p>This method will block until either the network was successfully formed or an error
* prevents the network form being formed.
*
* <p>Upon success, the interface will be up and attached to the newly formed network.
*
* @see #join(LowpanProvision)
*/
public void form(@NonNull LowpanProvision provision) throws LowpanException {
try {
Map<String, Object> parameters = new HashMap();
provision.addToMap(parameters);
mBinder.form(parameters);
} catch (RemoteException x) {
throwAsPublicException(x);
} catch (ServiceSpecificException x) {
throwAsPublicException(x);
}
}
/**
* Attempts to join a new network with the given network information. This method will block
* until either the network was successfully joined or an error prevented the network from being
* formed. Upon success, the interface will be up and attached to the newly joined network.
*
* <p>Note that “joining” is distinct from “attaching”: Joining requires at least one other peer
* device to be present in order for the operation to complete successfully.
*/
public void join(@NonNull LowpanProvision provision) throws LowpanException {
try {
Map<String, Object> parameters = new HashMap();
provision.addToMap(parameters);
mBinder.join(parameters);
} catch (RemoteException x) {
throwAsPublicException(x);
} catch (ServiceSpecificException x) {
throwAsPublicException(x);
}
}
/**
* Attaches to the network described by identity and credential. This is similar to {@link
* #join}, except that (assuming the identity and credential are valid) it will always succeed
* and provision the interface, even if there are no peers nearby.
*
* <p>This method will block execution until the operation has completed.
*/
public void attach(@NonNull LowpanProvision provision) throws LowpanException {
if (ROLE_DETACHED.equals(getRole())) {
Map<String, Object> parameters = new HashMap();
provision.addToMap(parameters);
setProperties(parameters);
setUp(true);
} else {
throw new LowpanException(LowpanException.LOWPAN_ALREADY);
}
}
/**
* Bring down the network interface and forget all non-volatile details about the current
* network.
*
* <p>This method will block execution until the operation has completed.
*/
public void leave() throws LowpanException {
try {
mBinder.leave();
} catch (RemoteException x) {
throwAsPublicException(x);
} catch (ServiceSpecificException x) {
throwAsPublicException(x);
}
}
/**
* Start a new commissioning session. Will fail if the interface is attached to a network or if
* the interface is disabled.
*/
public @NonNull LowpanCommissioningSession startCommissioningSession(
@NonNull LowpanBeaconInfo beaconInfo) throws LowpanException {
/* TODO: Implement startCommissioningSession */
throw new LowpanException(LowpanException.LOWPAN_FEATURE_NOT_SUPPORTED);
}
/**
* Reset this network interface as if it has been power cycled. Will bring the network interface
* down if it was previously up. Will not erase any non-volatile settings.
*
* <p>This method will block execution until the operation has completed.
*
* @hide
*/
public void reset() throws LowpanException {
try {
mBinder.reset();
} catch (RemoteException x) {
throwAsPublicException(x);
} catch (ServiceSpecificException x) {
throwAsPublicException(x);
}
}
//////////////////////////////////////////////////////////////////////////
// Public Getters and Setters
/**
* Returns the name of this network interface.
*
* <p>Will return empty string if this interface is no longer viable.
*/
@NonNull
public String getName() {
try {
return mBinder.getName();
} catch (RemoteException x) {
// Catch and ignore all binder exceptions
// when fetching the name.
Log.e(TAG, x.toString());
} catch (ServiceSpecificException x) {
// Catch and ignore all service-specific exceptions
// when fetching the name.
Log.e(TAG, x.toString());
}
return "";
}
/**
* Indicates if the interface is enabled or disabled.
*
* @see #setEnabled
* @see android.net.lowpan.LowpanException#LOWPAN_DISABLED
*/
public boolean isEnabled() {
try {
return getPropertyAsBoolean(LowpanProperties.KEY_INTERFACE_ENABLED);
} catch (LowpanException x) {
return false;
}
}
/**
* Enables or disables the LoWPAN interface. When disabled, the interface is put into a low-power
* state and all commands that require the NCP to be queried will fail with {@link
* android.net.lowpan.LowpanException#LOWPAN_DISABLED}.
*
* @see #isEnabled
* @see android.net.lowpan.LowpanException#LOWPAN_DISABLED
* @hide
*/
public void setEnabled(boolean enabled) throws LowpanException {
setProperty(LowpanProperties.KEY_INTERFACE_ENABLED, enabled);
}
/**
* Indicates if the network interface is up or down.
*
* @hide
*/
public boolean isUp() {
try {
return getPropertyAsBoolean(LowpanProperties.KEY_INTERFACE_UP);
} catch (LowpanException x) {
return false;
}
}
/**
* Bring up or shut down the network interface.
*
* <p>This method brings up or shuts down the network interface, attaching or (gracefully)
* detaching from the currently configured LoWPAN network as appropriate.
*
* @hide
*/
public void setUp(boolean interfaceUp) throws LowpanException {
setProperty(LowpanProperties.KEY_INTERFACE_UP, interfaceUp);
}
/**
* Indicates if there is at least one peer in range.
*
* @return <code>true</code> if we have at least one other peer in range, <code>false</code>
* otherwise.
*/
public boolean isConnected() {
try {
return getPropertyAsBoolean(LowpanProperties.KEY_INTERFACE_CONNECTED);
} catch (LowpanException x) {
return false;
}
}
/**
* Indicates if this interface is currently commissioned onto an existing network. If the
* interface is commissioned, the interface may be brought up using setUp().
*/
public boolean isCommissioned() {
try {
return getPropertyAsBoolean(LowpanProperties.KEY_INTERFACE_COMMISSIONED);
} catch (LowpanException x) {
return false;
}
}
/**
* Get interface state
*
* <h3>State Diagram</h3>
*
* <img src="LowpanInterface-1.png" />
*
* @return The current state of the interface.
* @see #STATE_OFFLINE
* @see #STATE_COMMISSIONING
* @see #STATE_ATTACHING
* @see #STATE_ATTACHED
* @see #STATE_FAULT
*/
public String getState() {
try {
return getProperty(LowpanProperties.KEY_INTERFACE_STATE);
} catch (LowpanException x) {
Log.e(TAG, x.toString());
return STATE_FAULT;
}
}
/** TODO: doc */
public LowpanIdentity getLowpanIdentity() {
LowpanIdentity.Builder builder = new LowpanIdentity.Builder();
try {
builder.updateFromMap(getProperties(NETWORK_PROPERTY_KEYS));
} catch (LowpanException x) {
// We ignore all LoWPAN-specitic exceptions here.
}
return builder.build();
}
/**
* TODO: doc
*
* @hide
*/
public void setLowpanIdentity(LowpanIdentity network) throws LowpanException {
Map<String, Object> map = new HashMap();
LowpanIdentity.addToMap(map, network);
setProperties(map);
}
/** TODO: doc */
@NonNull
public String getRole() {
String role = null;
try {
role = getProperty(LowpanProperties.KEY_NETWORK_ROLE);
} catch (LowpanException x) {
// We ignore all LoWPAN-specitic exceptions here.
Log.e(TAG, x.toString());
}
if (role == null) {
role = ROLE_DETACHED;
}
return role;
}
/** TODO: doc */
@Nullable
public LowpanCredential getLowpanCredential() {
LowpanCredential credential = null;
try {
Integer keyIndex = getProperty(LowpanProperties.KEY_NETWORK_MASTER_KEY_INDEX);
if (keyIndex == null) {
credential =
LowpanCredential.createMasterKey(
getProperty(LowpanProperties.KEY_NETWORK_MASTER_KEY));
} else {
credential =
LowpanCredential.createMasterKey(
getProperty(LowpanProperties.KEY_NETWORK_MASTER_KEY),
keyIndex.intValue());
}
} catch (LowpanException x) {
// We ignore all LoWPAN-specitic exceptions here.
Log.e(TAG, x.toString());
}
return credential;
}
/**
* TODO: doc
*
* @hide
*/
public void setLowpanCredential(LowpanCredential networkCredential) throws LowpanException {
Map<String, Object> map = new HashMap();
networkCredential.addToMap(map);
setProperties(map);
}
//////////////////////////////////////////////////////////////////////////
// Listener Support
/**
* Registers a subclass of {@link LowpanInterface.Callback} to receive events.
*
* @param cb Subclass of {@link LowpanInterface.Callback} which will receive events.
* @param handler If not <code>null</code>, events will be dispatched via the given handler
* object. If <code>null</code>, the thread upon which events will be dispatched is
* unspecified.
* @see #registerCallback(Callback)
* @see #unregisterCallback(Callback)
*/
public void registerCallback(@NonNull Callback cb, @Nullable Handler handler) {
ILowpanInterfaceListener.Stub listenerBinder =
new ILowpanInterfaceListener.Stub() {
public void onPropertiesChanged(Map<String, Object> properties) {
Runnable runnable =
new Runnable() {
@Override
public void run() {
for (String key : (Set<String>) properties.keySet()) {
Object value = properties.get(key);
switch (key) {
case ILowpanInterface.KEY_INTERFACE_ENABLED:
cb.onEnabledChanged(
((Boolean) value).booleanValue());
break;
case ILowpanInterface.KEY_INTERFACE_UP:
cb.onUpChanged(
((Boolean) value).booleanValue());
break;
case ILowpanInterface.KEY_INTERFACE_CONNECTED:
cb.onConnectedChanged(
((Boolean) value).booleanValue());
break;
case ILowpanInterface.KEY_INTERFACE_STATE:
cb.onStateChanged((String) value);
break;
case ILowpanInterface.KEY_NETWORK_NAME:
case ILowpanInterface.KEY_NETWORK_PANID:
case ILowpanInterface.KEY_NETWORK_XPANID:
case ILowpanInterface.KEY_CHANNEL:
cb.onLowpanIdentityChanged(getLowpanIdentity());
break;
case ILowpanInterface.KEY_NETWORK_ROLE:
cb.onRoleChanged(value.toString());
break;
}
}
cb.onPropertiesChanged(properties);
}
};
if (handler != null) {
handler.post(runnable);
} else {
runnable.run();
}
}
};
try {
mBinder.addListener(listenerBinder);
} catch (RemoteException x) {
// Log and ignore. If this happens, this interface
// is likely dead anyway.
Log.e(TAG, x.toString());
}
synchronized (mListenerMap) {
mListenerMap.put(System.identityHashCode(cb), listenerBinder);
}
}
/**
* Registers a subclass of {@link LowpanInterface.Callback} to receive events.
*
* <p>The thread upon which events will be dispatched is unspecified.
*
* @param cb Subclass of {@link LowpanInterface.Callback} which will receive events.
* @see #registerCallback(Callback, Handler)
* @see #unregisterCallback(Callback)
*/
public void registerCallback(Callback cb) {
registerCallback(cb, null);
}
/**
* Unregisters a previously registered callback class.
*
* @param cb Subclass of {@link LowpanInterface.Callback} which was previously registered to
* receive events.
* @see #registerCallback(Callback, Handler)
* @see #registerCallback(Callback)
*/
public void unregisterCallback(Callback cb) {
int hashCode = System.identityHashCode(cb);
ILowpanInterfaceListener listenerBinder = mListenerMap.get(hashCode);
if (listenerBinder != null) {
synchronized (mListenerMap) {
mListenerMap.remove(hashCode);
}
try {
mBinder.removeListener(listenerBinder);
} catch (RemoteException x) {
// Catch and ignore all binder exceptions
Log.e(TAG, x.toString());
}
}
}
//////////////////////////////////////////////////////////////////////////
// Active and Passive Scanning
/**
* Creates a new {@link android.net.lowpan.LowpanScanner} object for this interface.
*
* <p>This method allocates a new unique object for each call.
*
* @see android.net.lowpan.LowpanScanner
*/
public @NonNull LowpanScanner createScanner() {
return new LowpanScanner(mBinder);
}
//////////////////////////////////////////////////////////////////////////
// Route Management
/**
* Advertise the given IP prefix as an on-mesh prefix.
*
* @hide
*/
public void addOnMeshPrefix(IpPrefix prefix, int flags) throws LowpanException {
try {
mBinder.addOnMeshPrefix(prefix, flags);
} catch (RemoteException x) {
throwAsPublicException(x);
} catch (ServiceSpecificException x) {
throwAsPublicException(x);
}
}
/**
* Remove an IP prefix previously advertised by this device from the list of advertised on-mesh
* prefixes.
*
* @hide
*/
public void removeOnMeshPrefix(IpPrefix prefix) {
try {
mBinder.removeOnMeshPrefix(prefix);
} catch (RemoteException x) {
// Catch and ignore all binder exceptions
Log.e(TAG, x.toString());
} catch (ServiceSpecificException x) {
// Catch and ignore all service exceptions
Log.e(TAG, x.toString());
}
}
/**
* Advertise this device to other devices on the mesh network as having a specific route to the
* given network. This device will then receive forwarded traffic for that network.
*
* @hide
*/
public void addExternalRoute(IpPrefix prefix, int flags) throws LowpanException {
try {
mBinder.addExternalRoute(prefix, flags);
} catch (RemoteException x) {
throwAsPublicException(x);
} catch (ServiceSpecificException x) {
throwAsPublicException(x);
}
}
/**
* Revoke a previously advertised specific route to the given network.
*
* @hide
*/
public void removeExternalRoute(IpPrefix prefix) {
try {
mBinder.removeExternalRoute(prefix);
} catch (RemoteException x) {
// Catch and ignore all binder exceptions
Log.e(TAG, x.toString());
} catch (ServiceSpecificException x) {
// Catch and ignore all service exceptions
Log.e(TAG, x.toString());
}
}
}