Files
openthread/tools/spinel-cli/spinel/codec.py
T
Nick Banks ad39a59ff9 Support Different Number of Max Children (#872)
* Add support for setting child max, for platforms that define a different maximum number of supported children.
2016-10-24 10:43:10 -07:00

974 lines
36 KiB
Python

#
# Copyright (c) 2016, The OpenThread Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
"""
Module providing a Spienl coder / decoder class.
"""
import os
import time
import logging
import threading
import traceback
import Queue
from struct import pack
from struct import unpack
from collections import namedtuple
from collections import defaultdict
import ipaddress
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
from scapy.layers.inet6 import IPv6
from scapy.layers.inet6 import ICMPv6EchoReply
import spinel.util as util
import spinel.config as CONFIG
from spinel.const import kThread
from spinel.const import SPINEL
from spinel.const import SPINEL_LAST_STATUS_MAP
from spinel.hdlc import Hdlc
from spinel.tun import TunInterface
FEATURE_USE_HDLC = 1
FEATURE_USE_SLACC = 1
TIMEOUT_PROP = 2
#=========================================
# SpinelCodec
#=========================================
# 0: DATATYPE_NULL
#'.': DATATYPE_VOID: Empty data type. Used internally.
#'b': DATATYPE_BOOL: Boolean value. Encoded in 8-bits as either 0x00 or 0x01.
# All other values are illegal.
#'C': DATATYPE_UINT8: Unsigned 8-bit integer.
#'c': DATATYPE_INT8: Signed 8-bit integer.
#'S': DATATYPE_UINT16: Unsigned 16-bit integer. (Little-endian)
#'s': DATATYPE_INT16: Signed 16-bit integer. (Little-endian)
#'L': DATATYPE_UINT32: Unsigned 32-bit integer. (Little-endian)
#'l': DATATYPE_INT32: Signed 32-bit integer. (Little-endian)
#'i': DATATYPE_UINT_PACKED: Packed Unsigned Integer. (See section 7.2)
#'6': DATATYPE_IPv6ADDR: IPv6 Address. (Big-endian)
#'E': DATATYPE_EUI64: EUI-64 Address. (Big-endian)
#'e': DATATYPE_EUI48: EUI-48 Address. (Big-endian)
#'D': DATATYPE_DATA: Arbitrary Data. (See section 7.3)
#'U': DATATYPE_UTF8: Zero-terminated UTF8-encoded string.
#'T': DATATYPE_STRUCT: Structured datatype. Compound type. (See section 7.4)
#'A': DATATYPE_ARRAY: Array of datatypes. Compound type. (See section 7.5)
class SpinelCodec(object):
""" A general coder / decoder class for Spinel protocol. """
@classmethod
def parse_b(cls, payload): return unpack("<B", payload[:1])[0]
@classmethod
def parse_c(cls, payload): return unpack("<b", payload[:1])[0]
@classmethod
def parse_C(cls, payload): return unpack("<B", payload[:1])[0]
@classmethod
def parse_s(cls, payload): return unpack("<h", payload[:2])[0]
@classmethod
def parse_S(cls, payload): return unpack("<H", payload[:2])[0]
@classmethod
def parse_l(cls, payload): return unpack("<l", payload[:4])[0]
@classmethod
def parse_L(cls, payload): return unpack("<L", payload[:4])[0]
@classmethod
def parse_6(cls, payload): return payload[:16]
@classmethod
def parse_E(cls, payload): return payload[:8]
@classmethod
def parse_e(cls, payload): return payload[:6]
@classmethod
def parse_U(cls, payload): return payload[:-1] # strip null
@classmethod
def parse_D(cls, payload): return payload
@classmethod
def parse_i(cls, payload):
""" Decode EXI integer format. """
value = 0
value_len = 0
value_mul = 1
while value_len < 4:
byte = ord(payload[value_len])
value += (byte & 0x7F) * value_mul
if byte < 0x80:
break
value_mul *= 0x80
value_len += 1
return (value, value_len + 1)
@classmethod
def parse_field(cls, payload, spinel_format):
map_decode = {
'b': cls.parse_b,
'c': cls.parse_c,
'C': cls.parse_C,
's': cls.parse_s,
'S': cls.parse_S,
'L': cls.parse_L,
'l': cls.parse_l,
'6': cls.parse_6,
'E': cls.parse_E,
'e': cls.parse_e,
'U': cls.parse_U,
'D': cls.parse_D,
'i': cls.parse_i,
}
try:
return map_decode[spinel_format[0]](payload)
except KeyError, _ex:
print traceback.format_exc()
return None
@classmethod
def encode_i(cls, data):
""" Encode EXI integer format. """
result = ""
while data:
value = data & 0x7F
data >>= 7
if data:
value |= 0x80
result = result + pack("<B", value)
return result
@classmethod
def encode_b(cls, value): return pack('B', value)
@classmethod
def encode_c(cls, value): return pack('B', value)
@classmethod
def encode_C(cls, value): return pack('B', value)
@classmethod
def encode_s(cls, value): return pack('<h', value)
@classmethod
def encode_S(cls, value): return pack('<H', value)
@classmethod
def encode_l(cls, value): return pack('<l', value)
@classmethod
def encode_L(cls, value): return pack('<L', value)
@classmethod
def encode_6(cls, value): return value[:16]
@classmethod
def encode_E(cls, value): return value[:8]
@classmethod
def encode_e(cls, value): return value[:6]
@classmethod
def encode_U(cls, value): return value + '\0'
@classmethod
def encode_D(cls, value): return value
@classmethod
def encode_field(cls, code, value):
map_encode = {
'b': cls.encode_b,
'c': cls.encode_c,
'C': cls.encode_C,
's': cls.encode_s,
'S': cls.encode_S,
'L': cls.encode_L,
'l': cls.encode_l,
'6': cls.encode_6,
'E': cls.encode_E,
'e': cls.encode_e,
'U': cls.encode_U,
'D': cls.encode_D,
'i': cls.encode_i,
}
try:
return map_encode[code](value)
except KeyError, _ex:
print traceback.format_exc()
return None
def next_code(self, spinel_format):
code = spinel_format[0]
spinel_format = spinel_format[1:]
# TODO: Handle T() and A()
return code, spinel_format
def encode_fields(self, spinel_format, *fields):
packed = ""
for field in fields:
code, spinel_format = self.next_code(spinel_format)
if not code:
break
packed += self.encode_field(code, field)
return packed
def encode_packet(self, command_id, payload=None, tid=SPINEL.HEADER_DEFAULT):
""" Encode the given payload as a Spinel frame. """
header = pack(">B", tid)
cmd = self.encode_i(command_id)
pkt = header + cmd + payload
return pkt
#=========================================
class SpinelPropertyHandler(SpinelCodec):
def LAST_STATUS(self, _, payload): return self.parse_i(payload)[0]
def PROTOCOL_VERSION(self, _wpan_api, payload): pass
def NCP_VERSION(self, _, payload): return self.parse_U(payload)
def INTERFACE_TYPE(self, _, payload): return self.parse_i(payload)[0]
def VENDOR_ID(self, _, payload): return self.parse_i(payload)[0]
def CAPS(self, _wpan_api, payload): pass
def INTERFACE_COUNT(self, _, payload): return self.parse_C(payload)
def POWER_STATE(self, _, payload): return self.parse_C(payload)
def HWADDR(self, _, payload): return self.parse_E(payload)
def LOCK(self, _, payload): return self.parse_b(payload)
def HBO_MEM_MAX(self, _, payload): return self.parse_L(payload)
def HBO_BLOCK_MAX(self, _, payload): return self.parse_S(payload)
def PHY_ENABLED(self, _, payload): return self.parse_b(payload)
def PHY_CHAN(self, _, payload): return self.parse_C(payload)
def PHY_CHAN_SUPPORTED(self, _wpan_api, payload): pass
def PHY_FREQ(self, _, payload): return self.parse_L(payload)
def PHY_CCA_THRESHOLD(self, _, payload): return self.parse_c(payload)
def PHY_TX_POWER(self, _, payload): return self.parse_c(payload)
def PHY_RSSI(self, _, payload): return self.parse_c(payload)
def MAC_SCAN_STATE(self, _, payload): return self.parse_C(payload)
def MAC_SCAN_MASK(self, _, payload): return self.parse_U(payload)
def MAC_SCAN_PERIOD(self, _, payload): return self.parse_S(payload)
def MAC_SCAN_BEACON(self, _, payload): return self.parse_U(payload)
def MAC_15_4_LADDR(self, _, payload): return self.parse_E(payload)
def MAC_15_4_SADDR(self, _, payload): return self.parse_S(payload)
def MAC_15_4_PANID(self, _, payload): return self.parse_S(payload)
def MAC_FILTER_MODE(self, _, payload): return self.parse_C(payload)
def MAC_RAW_STREAM_ENABLED(self, _, payload):
return self.parse_b(payload)
def MAC_WHITELIST(self, _, payload): pass
def MAC_WHITELIST_ENABLED(self, _, payload):
return self.parse_b(payload)
def NET_SAVED(self, _, payload): return self.parse_b(payload)
def NET_IF_UP(self, _, payload): return self.parse_b(payload)
def NET_STACK_UP(self, _, payload): return self.parse_C(payload)
def NET_ROLE(self, _, payload): return self.parse_C(payload)
def NET_NETWORK_NAME(self, _, payload): return self.parse_U(payload)
def NET_XPANID(self, _, payload): return self.parse_D(payload)
def NET_MASTER_KEY(self, _, payload): return self.parse_D(payload)
def NET_KEY_SEQUENCE_COUNTER(self, _, payload): return self.parse_L(payload)
def NET_PARTITION_ID(self, _, payload): return self.parse_L(payload)
def NET_KEY_SWITCH_GUARDTIME(self, _, payload): return self.parse_L(payload)
def THREAD_LEADER_ADDR(self, _, payload): return self.parse_6(payload)
def THREAD_PARENT(self, _wpan_api, payload): pass
def THREAD_CHILD_TABLE(self, _, payload): return self.parse_D(payload)
def THREAD_LEADER_RID(self, _, payload): return self.parse_C(payload)
def THREAD_LEADER_WEIGHT(self, _, payload):
return self.parse_C(payload)
def THREAD_LOCAL_LEADER_WEIGHT(self, _, payload):
return self.parse_C(payload)
def THREAD_NETWORK_DATA(self, _, payload):
return self.parse_D(payload)
def THREAD_NETWORK_DATA_VERSION(self, _wpan_api, payload): pass
def THREAD_STABLE_NETWORK_DATA(self, _wpan_api, payload): pass
def THREAD_STABLE_NETWORK_DATA_VERSION(self, _wpan_api, payload): pass
def __init__(self):
self.autoAddresses = set()
self.wpan_api = None
self.__queue_prefix = Queue.Queue()
self.prefix_thread = threading.Thread(target=self.__run_prefix_handler)
self.prefix_thread.setDaemon(True)
self.prefix_thread.start()
def handle_ipaddr_remove(self, ipaddr):
valid = 1
preferred = 1
flags = 0
prefix_len = 64 # always use /64
arr = self.encode_fields('6CLLC',
ipaddr.ip.packed,
prefix_len,
valid,
preferred,
flags)
self.wpan_api.prop_remove_async(SPINEL.PROP_IPV6_ADDRESS_TABLE,
arr, str(len(arr)) + 's',
SPINEL.HEADER_EVENT_HANDLER)
def handle_ipaddr_insert(self, prefix, prefix_len, _stable, flags, _is_local):
""" Add an ip address for each prefix on prefix change. """
ipaddr_str = str(ipaddress.IPv6Address(prefix)) + \
str(self.wpan_api.nodeid)
if CONFIG.DEBUG_LOG_PROP:
print "\n>>>> new PREFIX add ipaddr: " + ipaddr_str
valid = 1
preferred = 1
flags = 0
ipaddr = ipaddress.IPv6Interface(unicode(ipaddr_str))
self.autoAddresses.add(ipaddr)
arr = self.encode_fields('6CLLC',
ipaddr.ip.packed,
prefix_len,
valid,
preferred,
flags)
self.wpan_api.prop_insert_async(SPINEL.PROP_IPV6_ADDRESS_TABLE,
arr, str(len(arr)) + 's',
SPINEL.HEADER_EVENT_HANDLER)
def handle_prefix_change(self, payload):
""" Automatically ipaddr add / remove addresses for each new prefix. """
# As done by cli.cpp Interpreter::HandleNetifStateChanged
# First parse payload and extract slaac prefix information.
pay = payload
Prefix = namedtuple("Prefix", "prefix prefixlen stable flags is_local")
prefixes = []
slaacPrefixSet = set()
while len(pay) >= 22:
(_structlen) = unpack('<H', pay[:2])
pay = pay[2:]
prefix = Prefix(*unpack('16sBBBB', pay[:20]))
if prefix.flags & kThread.PrefixSlaacFlag:
net6 = ipaddress.IPv6Network(prefix.prefix)
net6 = net6.supernet(new_prefix=prefix.prefixlen)
slaacPrefixSet.add(net6)
prefixes.append(prefix)
pay = pay[20:]
for prefix in prefixes:
self.handle_ipaddr_insert(*prefix)
if CONFIG.DEBUG_LOG_PROP:
print "\n========= PREFIX ============"
print "ipaddrs: " + str(self.autoAddresses)
print "slaac prefix set: " + str(slaacPrefixSet)
print "==============================\n"
# ==> ipaddrs - query current addresses
#
# for ipaddr in ipaddrs:
# if lifetime > 0 and not in slaac prefixes
# ==> remove
for ipaddr in self.autoAddresses:
if not any(ipaddr in prefix for prefix in slaacPrefixSet):
self.handle_ipaddr_remove(ipaddr)
# for slaac prefix in prefixes:
# if no ipaddr with lifetime > 0 in prefix:
# ==> add
def __run_prefix_handler(self):
while 1:
(wpan_api, payload) = self.__queue_prefix.get(True)
self.wpan_api = wpan_api
self.handle_prefix_change(payload)
self.__queue_prefix.task_done()
def THREAD_ON_MESH_NETS(self, wpan_api, payload):
if FEATURE_USE_SLACC:
# Kick prefix handler thread to allow serial rx thread to work.
self.__queue_prefix.put_nowait((wpan_api, payload))
return self.parse_D(payload)
def THREAD_LOCAL_ROUTES(self, _wpan_api, payload): pass
def THREAD_ASSISTING_PORTS(self, _wpan_api, payload): pass
def THREAD_ALLOW_LOCAL_NET_DATA_CHANGE(self, _, payload):
return self.parse_b(payload)
def THREAD_MODE(self, _, payload): return self.parse_C(payload)
def THREAD_CHILD_COUNT_MAX(self, _, payload): return self.parse_C(payload)
def THREAD_CHILD_TIMEOUT(self, _, payload): return self.parse_L(payload)
def THREAD_RLOC16(self, _, payload): return self.parse_S(payload)
def THREAD_ROUTER_UPGRADE_THRESHOLD(self, _, payload):
return self.parse_C(payload)
def THREAD_ROUTER_DOWNGRADE_THRESHOLD(self, _, payload):
return self.parse_C(payload)
def THREAD_ROUTER_SELECTION_JITTER(self, _, payload):
return self.parse_C(payload)
def THREAD_CONTEXT_REUSE_DELAY(self, _, payload):
return self.parse_L(payload)
def THREAD_NETWORK_ID_TIMEOUT(self, _, payload):
return self.parse_C(payload)
def THREAD_ACTIVE_ROUTER_IDS(self, _, payload):
return self.parse_D(payload)
def THREAD_RLOC16_DEBUG_PASSTHRU(self, _, payload):
return self.parse_b(payload)
def MESHCOP_JOINER_ENABLE(self, _, payload):
return self.parse_b(payload)
def MESHCOP_JOINER_CREDENTIAL(self, _, payload):
return self.parse_D(payload)
def MESHCOP_JOINER_URL(self, _, payload):
return self.parse_U(payload)
def MESHCOP_BORDER_AGENT_ENABLE(self, _, payload):
return self.parse_b(payload)
def IPV6_LL_ADDR(self, _, payload): return self.parse_6(payload)
def IPV6_ML_ADDR(self, _, payload): return self.parse_6(payload)
def IPV6_ML_PREFIX(self, _, payload): return self.parse_E(payload)
def IPV6_ADDRESS_TABLE(self, _, payload): return self.parse_D(payload)
def IPV6_ROUTE_TABLE(self, _, payload): return self.parse_D(payload)
def IPv6_ICMP_PING_OFFLOAD(self, _, payload):
return self.parse_b(payload)
def STREAM_DEBUG(self, _, payload): return self.parse_U(payload)
def STREAM_RAW(self, _, payload): return self.parse_D(payload)
def STREAM_NET(self, _, payload): return self.parse_D(payload)
def STREAM_NET_INSECURE(self, _, payload): return self.parse_D(payload)
def PIB_PHY_CHANNELS_SUPPORTED(self, _wpan_api, payload): pass
def PIB_MAC_PROMISCUOUS_MODE(self, _wpan_api, payload): pass
def PIB_MAC_SECURITY_ENABLED(self, _wpan_api, payload): pass
#=========================================
class SpinelCommandHandler(SpinelCodec):
def handle_prop(self, wpan_api, name, payload, tid):
(prop_id, prop_len) = self.parse_i(payload)
try:
handler = SPINEL_PROP_DISPATCH[prop_id]
prop_name = handler.__name__
prop_value = handler(wpan_api, payload[prop_len:])
if CONFIG.DEBUG_LOG_PROP:
# Generic output
if isinstance(prop_value, basestring):
prop_value_str = util.hexify_str(prop_value)
logging.debug("PROP_VALUE_%s [tid=%d]: %s = %s",
name, (tid & 0xF), prop_name, prop_value_str)
else:
prop_value_str = str(prop_value)
logging.debug("PROP_VALUE_%s [tid=%d]: %s = %s",
name, (tid & 0xF), prop_name, prop_value_str)
# Extend output for certain properties.
if prop_id == SPINEL.PROP_LAST_STATUS:
logging.debug(SPINEL_LAST_STATUS_MAP[prop_value])
if CONFIG.DEBUG_LOG_PKT:
if ((prop_id == SPINEL.PROP_STREAM_NET) or
(prop_id == SPINEL.PROP_STREAM_NET_INSECURE)):
logging.debug("PROP_VALUE_" + name + ": " + prop_name)
pkt = IPv6(prop_value[2:])
pkt.show()
elif prop_id == SPINEL.PROP_STREAM_DEBUG:
logging.debug("DEBUG: " + prop_value)
if wpan_api:
wpan_api.queue_add(prop_id, prop_value, tid)
else:
print "no wpan_api"
except Exception as _ex:
prop_name = "Property Unknown"
logging.info("\n%s (%i): ", prop_name, prop_id)
print traceback.format_exc()
def PROP_VALUE_IS(self, wpan_api, payload, tid):
self.handle_prop(wpan_api, "IS", payload, tid)
def PROP_VALUE_INSERTED(self, wpan_api, payload, tid):
self.handle_prop(wpan_api, "INSERTED", payload, tid)
def PROP_VALUE_REMOVED(self, wpan_api, payload, tid):
self.handle_prop(wpan_api, "REMOVED", payload, tid)
WPAN_CMD_HANDLER = SpinelCommandHandler()
SPINEL_COMMAND_DISPATCH = {
SPINEL.RSP_PROP_VALUE_IS: WPAN_CMD_HANDLER.PROP_VALUE_IS,
SPINEL.RSP_PROP_VALUE_INSERTED: WPAN_CMD_HANDLER.PROP_VALUE_INSERTED,
SPINEL.RSP_PROP_VALUE_REMOVED: WPAN_CMD_HANDLER.PROP_VALUE_REMOVED,
}
WPAN_PROP_HANDLER = SpinelPropertyHandler()
SPINEL_PROP_DISPATCH = {
SPINEL.PROP_LAST_STATUS: WPAN_PROP_HANDLER.LAST_STATUS,
SPINEL.PROP_PROTOCOL_VERSION: WPAN_PROP_HANDLER.PROTOCOL_VERSION,
SPINEL.PROP_NCP_VERSION: WPAN_PROP_HANDLER.NCP_VERSION,
SPINEL.PROP_INTERFACE_TYPE: WPAN_PROP_HANDLER.INTERFACE_TYPE,
SPINEL.PROP_VENDOR_ID: WPAN_PROP_HANDLER.VENDOR_ID,
SPINEL.PROP_CAPS: WPAN_PROP_HANDLER.CAPS,
SPINEL.PROP_INTERFACE_COUNT: WPAN_PROP_HANDLER.INTERFACE_COUNT,
SPINEL.PROP_POWER_STATE: WPAN_PROP_HANDLER.POWER_STATE,
SPINEL.PROP_HWADDR: WPAN_PROP_HANDLER.HWADDR,
SPINEL.PROP_LOCK: WPAN_PROP_HANDLER.LOCK,
SPINEL.PROP_HBO_MEM_MAX: WPAN_PROP_HANDLER.HBO_MEM_MAX,
SPINEL.PROP_HBO_BLOCK_MAX: WPAN_PROP_HANDLER.HBO_BLOCK_MAX,
SPINEL.PROP_PHY_ENABLED: WPAN_PROP_HANDLER.PHY_ENABLED,
SPINEL.PROP_PHY_CHAN: WPAN_PROP_HANDLER.PHY_CHAN,
SPINEL.PROP_PHY_CHAN_SUPPORTED: WPAN_PROP_HANDLER.PHY_CHAN_SUPPORTED,
SPINEL.PROP_PHY_FREQ: WPAN_PROP_HANDLER.PHY_FREQ,
SPINEL.PROP_PHY_CCA_THRESHOLD: WPAN_PROP_HANDLER.PHY_CCA_THRESHOLD,
SPINEL.PROP_PHY_TX_POWER: WPAN_PROP_HANDLER.PHY_TX_POWER,
SPINEL.PROP_PHY_RSSI: WPAN_PROP_HANDLER.PHY_RSSI,
SPINEL.PROP_MAC_SCAN_STATE: WPAN_PROP_HANDLER.MAC_SCAN_STATE,
SPINEL.PROP_MAC_SCAN_MASK: WPAN_PROP_HANDLER.MAC_SCAN_MASK,
SPINEL.PROP_MAC_SCAN_PERIOD: WPAN_PROP_HANDLER.MAC_SCAN_PERIOD,
SPINEL.PROP_MAC_SCAN_BEACON: WPAN_PROP_HANDLER.MAC_SCAN_BEACON,
SPINEL.PROP_MAC_15_4_LADDR: WPAN_PROP_HANDLER.MAC_15_4_LADDR,
SPINEL.PROP_MAC_15_4_SADDR: WPAN_PROP_HANDLER.MAC_15_4_SADDR,
SPINEL.PROP_MAC_15_4_PANID: WPAN_PROP_HANDLER.MAC_15_4_PANID,
SPINEL.PROP_MAC_RAW_STREAM_ENABLED: WPAN_PROP_HANDLER.MAC_RAW_STREAM_ENABLED,
SPINEL.PROP_MAC_FILTER_MODE: WPAN_PROP_HANDLER.MAC_FILTER_MODE,
SPINEL.PROP_MAC_WHITELIST: WPAN_PROP_HANDLER.MAC_WHITELIST,
SPINEL.PROP_MAC_WHITELIST_ENABLED: WPAN_PROP_HANDLER.MAC_WHITELIST_ENABLED,
SPINEL.PROP_NET_SAVED: WPAN_PROP_HANDLER.NET_SAVED,
SPINEL.PROP_NET_IF_UP: WPAN_PROP_HANDLER.NET_IF_UP,
SPINEL.PROP_NET_STACK_UP: WPAN_PROP_HANDLER.NET_STACK_UP,
SPINEL.PROP_NET_ROLE: WPAN_PROP_HANDLER.NET_ROLE,
SPINEL.PROP_NET_NETWORK_NAME: WPAN_PROP_HANDLER.NET_NETWORK_NAME,
SPINEL.PROP_NET_XPANID: WPAN_PROP_HANDLER.NET_XPANID,
SPINEL.PROP_NET_MASTER_KEY: WPAN_PROP_HANDLER.NET_MASTER_KEY,
SPINEL.PROP_NET_KEY_SEQUENCE_COUNTER: WPAN_PROP_HANDLER.NET_KEY_SEQUENCE_COUNTER,
SPINEL.PROP_NET_PARTITION_ID: WPAN_PROP_HANDLER.NET_PARTITION_ID,
SPINEL.PROP_NET_KEY_SWITCH_GUARDTIME: WPAN_PROP_HANDLER.NET_KEY_SWITCH_GUARDTIME,
SPINEL.PROP_THREAD_LEADER_ADDR: WPAN_PROP_HANDLER.THREAD_LEADER_ADDR,
SPINEL.PROP_THREAD_PARENT: WPAN_PROP_HANDLER.THREAD_PARENT,
SPINEL.PROP_THREAD_CHILD_TABLE: WPAN_PROP_HANDLER.THREAD_CHILD_TABLE,
SPINEL.PROP_THREAD_LEADER_RID: WPAN_PROP_HANDLER.THREAD_LEADER_RID,
SPINEL.PROP_THREAD_LEADER_WEIGHT: WPAN_PROP_HANDLER.THREAD_LEADER_WEIGHT,
SPINEL.PROP_THREAD_LOCAL_LEADER_WEIGHT: WPAN_PROP_HANDLER.THREAD_LOCAL_LEADER_WEIGHT,
SPINEL.PROP_THREAD_NETWORK_DATA: WPAN_PROP_HANDLER.THREAD_NETWORK_DATA,
SPINEL.PROP_THREAD_NETWORK_DATA_VERSION: WPAN_PROP_HANDLER.THREAD_NETWORK_DATA_VERSION,
SPINEL.PROP_THREAD_STABLE_NETWORK_DATA: WPAN_PROP_HANDLER.THREAD_STABLE_NETWORK_DATA,
SPINEL.PROP_THREAD_STABLE_NETWORK_DATA_VERSION:
WPAN_PROP_HANDLER.THREAD_STABLE_NETWORK_DATA_VERSION,
SPINEL.PROP_THREAD_ON_MESH_NETS: WPAN_PROP_HANDLER.THREAD_ON_MESH_NETS,
SPINEL.PROP_THREAD_LOCAL_ROUTES: WPAN_PROP_HANDLER.THREAD_LOCAL_ROUTES,
SPINEL.PROP_THREAD_ASSISTING_PORTS: WPAN_PROP_HANDLER.THREAD_ASSISTING_PORTS,
SPINEL.PROP_THREAD_ALLOW_LOCAL_NET_DATA_CHANGE:
WPAN_PROP_HANDLER.THREAD_ALLOW_LOCAL_NET_DATA_CHANGE,
SPINEL.PROP_THREAD_MODE: WPAN_PROP_HANDLER.THREAD_MODE,
SPINEL.PROP_THREAD_CHILD_COUNT_MAX: WPAN_PROP_HANDLER.THREAD_CHILD_COUNT_MAX,
SPINEL.PROP_THREAD_CHILD_TIMEOUT: WPAN_PROP_HANDLER.THREAD_CHILD_TIMEOUT,
SPINEL.PROP_THREAD_RLOC16: WPAN_PROP_HANDLER.THREAD_RLOC16,
SPINEL.PROP_THREAD_ROUTER_UPGRADE_THRESHOLD: WPAN_PROP_HANDLER.THREAD_ROUTER_UPGRADE_THRESHOLD,
SPINEL.PROP_THREAD_ROUTER_DOWNGRADE_THRESHOLD:
WPAN_PROP_HANDLER.THREAD_ROUTER_DOWNGRADE_THRESHOLD,
SPINEL.PROP_THREAD_ROUTER_SELECTION_JITTER: WPAN_PROP_HANDLER.THREAD_ROUTER_SELECTION_JITTER,
SPINEL.PROP_THREAD_CONTEXT_REUSE_DELAY: WPAN_PROP_HANDLER.THREAD_CONTEXT_REUSE_DELAY,
SPINEL.PROP_THREAD_NETWORK_ID_TIMEOUT: WPAN_PROP_HANDLER.THREAD_NETWORK_ID_TIMEOUT,
SPINEL.PROP_THREAD_ACTIVE_ROUTER_IDS: WPAN_PROP_HANDLER.THREAD_ACTIVE_ROUTER_IDS,
SPINEL.PROP_THREAD_RLOC16_DEBUG_PASSTHRU: WPAN_PROP_HANDLER.THREAD_RLOC16_DEBUG_PASSTHRU,
SPINEL.PROP_MESHCOP_JOINER_ENABLE: WPAN_PROP_HANDLER.MESHCOP_JOINER_ENABLE,
SPINEL.PROP_MESHCOP_JOINER_CREDENTIAL: WPAN_PROP_HANDLER.MESHCOP_JOINER_CREDENTIAL,
SPINEL.PROP_MESHCOP_JOINER_URL: WPAN_PROP_HANDLER.MESHCOP_JOINER_URL,
SPINEL.PROP_MESHCOP_BORDER_AGENT_ENABLE: WPAN_PROP_HANDLER.MESHCOP_BORDER_AGENT_ENABLE,
SPINEL.PROP_IPV6_LL_ADDR: WPAN_PROP_HANDLER.IPV6_LL_ADDR,
SPINEL.PROP_IPV6_ML_ADDR: WPAN_PROP_HANDLER.IPV6_ML_ADDR,
SPINEL.PROP_IPV6_ML_PREFIX: WPAN_PROP_HANDLER.IPV6_ML_PREFIX,
SPINEL.PROP_IPV6_ADDRESS_TABLE: WPAN_PROP_HANDLER.IPV6_ADDRESS_TABLE,
SPINEL.PROP_IPV6_ROUTE_TABLE: WPAN_PROP_HANDLER.IPV6_ROUTE_TABLE,
SPINEL.PROP_IPv6_ICMP_PING_OFFLOAD: WPAN_PROP_HANDLER.IPv6_ICMP_PING_OFFLOAD,
SPINEL.PROP_STREAM_DEBUG: WPAN_PROP_HANDLER.STREAM_DEBUG,
SPINEL.PROP_STREAM_RAW: WPAN_PROP_HANDLER.STREAM_RAW,
SPINEL.PROP_STREAM_NET: WPAN_PROP_HANDLER.STREAM_NET,
SPINEL.PROP_STREAM_NET_INSECURE: WPAN_PROP_HANDLER.STREAM_NET_INSECURE,
SPINEL.PROP_PIB_15_4_PHY_CHANNELS_SUPPORTED: WPAN_PROP_HANDLER.PIB_PHY_CHANNELS_SUPPORTED,
SPINEL.PROP_PIB_15_4_MAC_PROMISCUOUS_MODE: WPAN_PROP_HANDLER.PIB_MAC_PROMISCUOUS_MODE,
SPINEL.PROP_PIB_15_4_MAC_SECURITY_ENABLED: WPAN_PROP_HANDLER.PIB_MAC_SECURITY_ENABLED,
}
class WpanApi(SpinelCodec):
""" Helper class to format wpan command packets """
def __init__(self, stream, nodeid, use_hdlc=FEATURE_USE_HDLC):
self.tun_if = None
self.stream = stream
self.nodeid = nodeid
self.use_hdlc = use_hdlc
if self.use_hdlc:
self.hdlc = Hdlc(self.stream)
# PARSER state
self.rx_pkt = []
# Fire up threads
self._reader_alive = True
self.tid_filter = set()
self.__queue_prop = defaultdict(Queue.Queue)
self.queue_register()
self.__start_reader()
def __del__(self):
self._reader_alive = False
def __start_reader(self):
"""Start reader thread"""
self._reader_alive = True
# start serial->console thread
self.receiver_thread = threading.Thread(target=self.stream_rx)
self.receiver_thread.setDaemon(True)
self.receiver_thread.start()
def transact(self, command_id, payload="", tid=SPINEL.HEADER_DEFAULT):
pkt = self.encode_packet(command_id, payload, tid)
if CONFIG.DEBUG_LOG_SERIAL:
msg = "TX Pay: (%i) %s " % (len(pkt), util.hexify_bytes(pkt))
logging.debug(msg)
if self.use_hdlc:
pkt = self.hdlc.encode(pkt)
self.stream_tx(pkt)
def parse_rx(self, pkt):
if not pkt:
return
if CONFIG.DEBUG_LOG_SERIAL:
msg = "RX Pay: (%i) %s " % (
len(pkt), str(map(util.hexify_int, pkt)))
logging.debug(msg)
length = len(pkt) - 2
if length < 0:
return
spkt = "".join(map(chr, pkt))
tid = self.parse_C(spkt[:1])
(cmd_id, cmd_length) = self.parse_i(spkt[1:])
pay_start = cmd_length + 1
payload = spkt[pay_start:]
try:
handler = SPINEL_COMMAND_DISPATCH[cmd_id]
cmd_name = handler.__name__
handler(self, payload, tid)
except Exception as _ex:
print traceback.format_exc()
cmd_name = "CB_Unknown"
logging.info("\n%s (%i): ", cmd_name, cmd_id)
if CONFIG.DEBUG_CMD_RESPONSE:
logging.info("\n%s (%i): ", cmd_name, cmd_id)
logging.info("===> %s", util.hexify_str(payload))
def stream_tx(self, pkt):
# Encapsulate lagging and Framer support in self.stream class.
self.stream.write(pkt)
def stream_rx(self):
""" Recieve thread and parser. """
while self._reader_alive:
if self.use_hdlc:
self.rx_pkt = self.hdlc.collect()
else:
# size=None: Assume stream will always deliver packets
pkt = self.stream.read(None)
self.rx_pkt = util.packed_to_array(pkt)
self.parse_rx(self.rx_pkt)
class PropertyItem(object):
""" Queue item for NCP response to property commands. """
def __init__(self, prop, value, tid):
self.prop = prop
self.value = value
self.tid = tid
def queue_register(self, tid=SPINEL.HEADER_DEFAULT):
self.tid_filter.add(tid)
return self.__queue_prop[tid]
def queue_wait_prepare(self, _prop_id, tid=SPINEL.HEADER_DEFAULT):
self.queue_clear(tid)
def queue_add(self, prop, value, tid):
# Asynchronous handlers don't actually add to queue.
if prop == SPINEL.PROP_STREAM_NET:
pkt = IPv6(value[2:])
if ICMPv6EchoReply in pkt:
timenow = int(round(time.time() * 1000)) & 0xFFFFFFFF
timedelta = (timenow - unpack('>I', pkt.data)[0])
print "\n%d bytes from %s: icmp_seq=%d hlim=%d time=%dms" % (
pkt.plen, pkt.src, pkt.seq, pkt.hlim, timedelta)
return
if tid not in self.tid_filter:
return
item = self.PropertyItem(prop, value, tid)
self.__queue_prop[tid].put_nowait(item)
def queue_clear(self, tid):
with self.__queue_prop[tid].mutex:
self.__queue_prop[tid].queue.clear()
def queue_wait_for_prop(self, _prop, tid=SPINEL.HEADER_DEFAULT, timeout=TIMEOUT_PROP):
try:
item = self.__queue_prop[tid].get(True, timeout)
# self.__queue_prop[tid].task_done()
except Queue.Empty:
item = None
return item
def if_up(self, nodeid='1'):
if os.geteuid() == 0:
self.tun_if = TunInterface(nodeid)
else:
print "Warning: superuser required to start tun interface."
def if_down(self):
if self.tun_if:
self.tun_if.close()
self.tun_if = None
def ip_send(self, pkt):
pay = self.encode_i(SPINEL.PROP_STREAM_NET)
pkt_len = len(pkt)
pay += pack("<H", pkt_len) # Start with length of IPv6 packet
pkt_len += 2 # Increment to include length word
pay += pack("%ds" % pkt_len, pkt) # Append packet after length
self.transact(SPINEL.CMD_PROP_VALUE_SET, pay)
def cmd_send(self, command_id, payload="", tid=SPINEL.HEADER_DEFAULT):
self.queue_wait_prepare(None, tid)
self.transact(command_id, payload, tid)
self.queue_wait_for_prop(None, tid)
def prop_change_async(self, cmd, prop_id, value, py_format='B',
tid=SPINEL.HEADER_DEFAULT):
pay = self.encode_i(prop_id)
if py_format != None:
pay += pack(py_format, value)
self.transact(cmd, pay, tid)
def prop_insert_async(self, prop_id, value, py_format='B',
tid=SPINEL.HEADER_DEFAULT):
self.prop_change_async(SPINEL.CMD_PROP_VALUE_INSERT, prop_id,
value, py_format, tid)
def prop_remove_async(self, prop_id, value, py_format='B',
tid=SPINEL.HEADER_DEFAULT):
self.prop_change_async(SPINEL.CMD_PROP_VALUE_REMOVE, prop_id,
value, py_format, tid)
def __prop_change_value(self, cmd, prop_id, value, py_format='B',
tid=SPINEL.HEADER_DEFAULT):
""" Utility routine to change a property value over SPINEL. """
self.queue_wait_prepare(prop_id, tid)
pay = self.encode_i(prop_id)
if py_format != None:
pay += pack(py_format, value)
self.transact(cmd, pay, tid)
result = self.queue_wait_for_prop(prop_id, tid)
if result:
return result.value
else:
return None
def prop_get_value(self, prop_id, tid=SPINEL.HEADER_DEFAULT):
""" Blocking routine to get a property value over SPINEL. """
if CONFIG.DEBUG_LOG_PROP:
handler = SPINEL_PROP_DISPATCH[prop_id]
prop_name = handler.__name__
print "PROP_VALUE_GET [tid=%d]: %s" % (tid & 0xF, prop_name)
return self.__prop_change_value(SPINEL.CMD_PROP_VALUE_GET, prop_id,
None, None, tid)
def prop_set_value(self, prop_id, value, py_format='B',
tid=SPINEL.HEADER_DEFAULT):
""" Blocking routine to set a property value over SPINEL. """
if CONFIG.DEBUG_LOG_PROP:
handler = SPINEL_PROP_DISPATCH[prop_id]
prop_name = handler.__name__
print "PROP_VALUE_SET [tid=%d]: %s" % (tid & 0xF, prop_name)
return self.__prop_change_value(SPINEL.CMD_PROP_VALUE_SET, prop_id,
value, py_format, tid)
def prop_insert_value(self, prop_id, value, py_format='B',
tid=SPINEL.HEADER_DEFAULT):
""" Blocking routine to insert a property value over SPINEL. """
if CONFIG.DEBUG_LOG_PROP:
handler = SPINEL_PROP_DISPATCH[prop_id]
prop_name = handler.__name__
print "PROP_VALUE_INSERT [tid=%d]: %s" % (tid & 0xF, prop_name)
return self.__prop_change_value(SPINEL.CMD_PROP_VALUE_INSERT, prop_id,
value, py_format, tid)
def prop_remove_value(self, prop_id, value, py_format='B',
tid=SPINEL.HEADER_DEFAULT):
""" Blocking routine to remove a property value over SPINEL. """
if CONFIG.DEBUG_LOG_PROP:
handler = SPINEL_PROP_DISPATCH[prop_id]
prop_name = handler.__name__
print "PROP_VALUE_REMOVE [tid=%d]: %s" % (tid & 0xF, prop_name)
return self.__prop_change_value(SPINEL.CMD_PROP_VALUE_REMOVE, prop_id,
value, py_format, tid)
def get_ipaddrs(self, tid=SPINEL.HEADER_DEFAULT):
"""
Return current list of ip addresses for the device.
"""
value = self.prop_get_value(SPINEL.PROP_IPV6_ADDRESS_TABLE, tid)
# TODO: clean up table parsing to be less hard-coded magic.
if value is None:
return None
size = 0x1B
addrs = [value[i:i + size] for i in xrange(0, len(value), size)]
ipaddrs = []
for addr in addrs:
addr = addr[2:18]
ipaddrs.append(ipaddress.IPv6Address(addr))
return ipaddrs