ncp: Added sniffer host tool. (#773)

*     ncp: Added sniffer host tool.

    Split spinel-cli out into a general spinel python library module.
    Added sniffer.py tool that leverages the spinel library module.
    Added some initial unit tests of spinel.py module using MockStream.

    From openthread root:

    ./tools/spinel-cli/sniffer.py -c 11 -n 1 -u /dev/ttyUSB0 | wireshark -k -i -
This commit is contained in:
Martin Turon
2016-10-14 15:08:59 -07:00
committed by Jonathan Hui
parent b6ebabf2e4
commit 3ea74a1977
23 changed files with 3318 additions and 1843 deletions
+8 -6
View File
@@ -45,6 +45,14 @@ cd /tmp || die
sudo -H pip install pexpect || die
pip install pexpect || die
# Packages used by ncp tools.
sudo -H pip install ipaddress || die
sudo -H pip install scapy || die
sudo -H pip install pyserial || die
pip install ipaddress || die
pip install scapy || die
pip install pyserial || die
[ $BUILD_TARGET != pretty-check ] || {
wget http://jaist.dl.sourceforge.net/project/astyle/astyle/astyle%202.05.1/astyle_2.05.1_linux.tar.gz || die
tar xzvf astyle_2.05.1_linux.tar.gz || die
@@ -70,12 +78,6 @@ cd /tmp || die
[ $BUILD_TARGET != posix-32-bit ] || {
sudo apt-get install g++-multilib || die
}
[ $BUILD_TARGET != posix-ncp ] || {
pip install ipaddress || die
pip install scapy || die
pip install pyserial || die
}
}
[ $TRAVIS_OS_NAME != osx ] || {
+1
View File
@@ -790,6 +790,7 @@ tools/harness-automation/Makefile
tools/harness-thci/Makefile
tools/spi-hdlc-adapter/Makefile
tools/spinel-cli/Makefile
tools/spinel-cli/spinel/Makefile
tests/Makefile
tests/scripts/Makefile
tests/unit/Makefile
+1
View File
@@ -40,6 +40,7 @@ DIST_SUBDIRS = \
# Always build (e.g. for 'make all') these subdirectories.
SUBDIRS = \
spinel-cli \
$(NULL)
if OPENTHREAD_TARGET_LINUX
+31 -2
View File
@@ -28,8 +28,37 @@
include $(abs_top_nlbuild_autotools_dir)/automake/pre.am
EXTRA_DIST = \
spinel-cli.py \
EXTRA_DIST = \
spinel-cli.py \
sniffer.py \
test_spinel.py \
$(NULL)
DIST_SUBDIRS = \
spinel \
$(NULL)
# Always build (e.g. for 'make all') these subdirectories.
SUBDIRS = \
$(NULL)
# Always pretty (e.g. for 'make pretty') these subdirectories.
PRETTY_SUBDIRS = \
$(NULL)
if OPENTHREAD_ENABLE_NCP
# List all essential script tests that MUST be run.
TESTS_ENVIRONMENT = \
$(NULL)
TESTS = \
test_spinel.py \
$(NULL)
endif # OPENTHREAD_ENABLE_NCP
include $(abs_top_nlbuild_autotools_dir)/automake/post.am
+96
View File
@@ -0,0 +1,96 @@
# Spinel Sniffer Reference
Any Spinel NCP node can be made into a promiscuous packet sniffer, and this
tool both intializes a device into this mode and outputs a pcap stream that
can be saved or piped directly into Wireshark.
## System Requirements
The tool has been tested on the following platforms:
| Platforms | Version |
|-----------|------------------|
| Ubuntu | 14.04 Trusty |
| Mac OS | 10.11 El Capitan |
| Language | Version |
|-----------|------------------|
| Python | 2.7.10 |
### Package Installation
```
sudo easy_install pip
sudo pip install ipaddress
sudo pip install scapy
sudo pip install pyserial
```
## Usage
### NAME
sniffer.py - shell tool for controlling OpenThread NCP instances
### SYNOPSIS
sniffer.py [-hupsnqvdxc]
### DESCRIPTION
```
-h, --help
Show this help message and exit
-u <UART>, --uart=<UART>
Open a serial connection to the OpenThread NCP device
where <UART> is a device path such as "/dev/ttyUSB0".
-p <PIPE>, --pipe=<PIPE>
Open a piped process connection to the OpenThread NCP device
where <PIPE> is the command to start an emulator, such as
"ot-ncp". Spinel-cli will communicate with the child process
via stdin/stdout.
-s <SOCKET>, --socket=<SOCKET>
Open a socket connection to the OpenThread NCP device
where <SOCKET> is the port to open.
This is useful for SPI configurations when used in conjunction
with a spinel spi-driver daemon.
Note: <SOCKET> will eventually map to hostname:port tuple.
-n NODEID, --nodeid=<NODEID>
The unique nodeid for the HOST and NCP instance.
-q, --quiet
Minimize debug and log output.
-v, --verbose
Maximize debug and log output.
-d <DEBUG_LEVEL>, --debug=<DEBUG_LEVEL>
Set the debug level. Enabling debug output is typically coupled with -x.
0: Supress all debug output. Required to stream to Wireshark.
1: Show spinel property changes and values.
2: Show spinel IPv6 packet bytes.
3: Show spinel raw packet bytes (after HDLC decoding).
4: Show spinel HDLC bytes.
5: Show spinel raw stream bytes: all serial traffic to NCP.
-x, --hex
Output packets as ASCII HEX rather than pcap.
-c, --channel
Set the channel upon which to listen.
```
## Quick Start
From openthread root:
```
sudo ./tools/spinel-cli/sniffer.py -c 11 -n 1 -u /dev/ttyUSB0 | wireshark -k -i -
```
This will connect to stock openthread ncp firmware over the given UART,
make the node into a promiscuous mode sniffer on the given channel,
open up wireshark, and start streaming packets into wireshark.
+154
View File
@@ -0,0 +1,154 @@
#!/usr/bin/python -u
#
# 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.
#
"""
Sniffer tool that outputs raw pcap.
Real-time stream to wireshark:
./sniffer.py | wireshark -k -i -
Save stream to file or pipe:
./sniffer.py > trace.pcap
"""
import sys
import optparse
import spinel.util as util
import spinel.config as CONFIG
from spinel.const import SPINEL
from spinel.codec import WpanApi
from spinel.stream import StreamOpen
from spinel.pcap import PcapCodec
# Nodeid is required to execute ot-ncp for its sim radio socket port.
# This is maximum that works for MacOS.
DEFAULT_NODEID = 34 # same as WELLKNOWN_NODE_ID
DEFAULT_CHANNEL = 11
def parse_args():
""" Parse command line arguments for this applications. """
args = sys.argv[1:]
opt_parser = optparse.OptionParser()
opt_parser.add_option("-u", "--uart", action="store",
dest="uart", type="string")
opt_parser.add_option("-p", "--pipe", action="store",
dest="pipe", type="string")
opt_parser.add_option("-s", "--socket", action="store",
dest="socket", type="string")
opt_parser.add_option("-n", "--nodeid", action="store",
dest="nodeid", type="string", default=str(DEFAULT_NODEID))
opt_parser.add_option("-q", "--quiet", action="store_true", dest="quiet")
opt_parser.add_option("-v", "--verbose", action="store_false", dest="verbose")
opt_parser.add_option("-d", "--debug", action="store",
dest="debug", type="int", default=CONFIG.DEBUG_ENABLE)
opt_parser.add_option("-x", "--hex", action="store_true", dest="hex")
opt_parser.add_option("-c", "--channel", action="store",
dest="channel", type="int", default=DEFAULT_CHANNEL)
return opt_parser.parse_args(args)
def sniffer_init(wpan_api, options):
"""" Send spinel commands to initialize sniffer node. """
wpan_api.queue_register(SPINEL.HEADER_DEFAULT)
wpan_api.queue_register(SPINEL.HEADER_ASYNC)
wpan_api.cmd_send(SPINEL.CMD_RESET)
wpan_api.prop_set_value(SPINEL.PROP_PHY_ENABLED, 1)
wpan_api.prop_set_value(SPINEL.PROP_MAC_FILTER_MODE, SPINEL.MAC_FILTER_MODE_MONITOR)
wpan_api.prop_set_value(SPINEL.PROP_PHY_CHAN, options.channel)
wpan_api.prop_set_value(SPINEL.PROP_MAC_15_4_PANID, 0xFFFF, 'H')
wpan_api.prop_set_value(SPINEL.PROP_MAC_RAW_STREAM_ENABLED, 1)
wpan_api.prop_set_value(SPINEL.PROP_NET_IF_UP, 1)
def main():
""" Top-level main for sniffer host-side tool. """
(options, remaining_args) = parse_args()
if options.debug:
CONFIG.debug_set_level(options.debug)
# Set default stream to pipe
stream_type = 'p'
stream_descriptor = "../../examples/apps/ncp/ot-ncp "+options.nodeid
if options.uart:
stream_type = 'u'
stream_descriptor = options.uart
elif options.socket:
stream_type = 's'
stream_descriptor = options.socket
elif options.pipe:
stream_type = 'p'
stream_descriptor = options.pipe
if options.nodeid:
stream_descriptor += " "+str(options.nodeid)
else:
if len(remaining_args) > 0:
stream_descriptor = " ".join(remaining_args)
stream = StreamOpen(stream_type, stream_descriptor, False)
if stream is None: exit()
wpan_api = WpanApi(stream, options.nodeid)
sniffer_init(wpan_api, options)
pcap = PcapCodec()
hdr = pcap.encode_header()
if options.hex:
hdr = util.hexify_str(hdr)+"\n"
sys.stdout.write(hdr)
sys.stdout.flush()
try:
tid = SPINEL.HEADER_ASYNC
prop_id = SPINEL.PROP_STREAM_RAW
while True:
result = wpan_api.queue_wait_for_prop(prop_id, tid)
if result and result.prop == prop_id:
length = wpan_api.parse_S(result.value)
pkt = result.value[2:2+length]
pkt = pcap.encode_frame(pkt)
if options.hex:
pkt = util.hexify_str(pkt)+"\n"
sys.stdout.write(pkt)
sys.stdout.flush()
except KeyboardInterrupt:
pass
if wpan_api:
wpan_api.stream.close()
if __name__ == "__main__":
main()
File diff suppressed because it is too large Load Diff
+51
View File
@@ -0,0 +1,51 @@
#
# 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.
#
include $(abs_top_nlbuild_autotools_dir)/automake/pre.am
EXTRA_DIST = \
__init__.py \
codec.py \
config.py \
const.py \
hdlc.py \
stream.py \
pcap.py \
tun.py \
util.py \
$(NULL)
EXTRA_DIST += \
tests.py \
test_codec.py \
test_hdlc.py \
test_stream.py \
test_sniffer.py \
$(NULL)
include $(abs_top_nlbuild_autotools_dir)/automake/post.am
+27
View File
@@ -0,0 +1,27 @@
#
# 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.
#
+967
View File
@@ -0,0 +1,967 @@
#
# 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(self, _, payload): return self.parse_L(payload)
def NET_PARTITION_ID(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_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: WPAN_PROP_HANDLER.NET_KEY_SEQUENCE,
SPINEL.PROP_NET_PARTITION_ID: WPAN_PROP_HANDLER.NET_PARTITION_ID,
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_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
+113
View File
@@ -0,0 +1,113 @@
#
# 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-wide logging configuration for spinel package. """
import logging
import logging.config
DEBUG_ENABLE = 0
DEBUG_TUN = 0
DEBUG_HDLC = 0
DEBUG_STREAM_TX = 0
DEBUG_STREAM_RX = 0
DEBUG_LOG_PKT = DEBUG_ENABLE
DEBUG_LOG_SERIAL = DEBUG_ENABLE
DEBUG_LOG_PROP = DEBUG_ENABLE
DEBUG_CMD_RESPONSE = 0
DEBUG_EXPERIMENTAL = 1
LOGGER = logging.getLogger(__name__)
logging.config.dictConfig({
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'minimal': {
'format': '%(message)s'
},
'standard': {
'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
},
},
'handlers': {
'console': {
#'level':'INFO',
'level': 'DEBUG',
'class': 'logging.StreamHandler',
},
#'syslog': {
# 'level':'DEBUG',
# 'class':'logging.handlers.SysLogHandler',
# 'address': '/dev/log'
#},
},
'loggers': {
'': {
'handlers': ['console'], # ,'syslog'],
'level': 'DEBUG',
'propagate': True
}
}
})
def debug_set_level(level):
""" Set logging level for spinel module. """
global DEBUG_ENABLE, DEBUG_LOG_PROP
global DEBUG_LOG_PKT, DEBUG_LOG_SERIAL
global DEBUG_STREAM_RX, DEBUG_STREAM_TX, DEBUG_HDLC
# Defaut to all logging disabled
DEBUG_ENABLE = 0
DEBUG_LOG_PROP = 0
DEBUG_LOG_PKT = 0
DEBUG_LOG_SERIAL = 0
DEBUG_HDLC = 0
DEBUG_STREAM_RX = 0
DEBUG_STREAM_TX = 0
if level:
DEBUG_ENABLE = level
if level >= 1:
DEBUG_LOG_PROP = 1
if level >= 2:
DEBUG_LOG_PKT = 1
if level >= 3:
DEBUG_LOG_SERIAL = 1
if level >= 4:
DEBUG_HDLC = 1
if level >= 5:
DEBUG_STREAM_RX = 1
DEBUG_STREAM_TX = 1
print "DEBUG_ENABLE = " + str(DEBUG_ENABLE)
+393
View File
@@ -0,0 +1,393 @@
#
# 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-wide constants for spinel package. """
class SPINEL(object):
""" Singular class that contains all Spinel constants. """
HEADER_ASYNC = 0x80
HEADER_DEFAULT = 0x81
HEADER_EVENT_HANDLER = 0x82
#=========================================
# Spinel Commands: Host -> NCP
#=========================================
CMD_NOOP = 0
CMD_RESET = 1
CMD_PROP_VALUE_GET = 2
CMD_PROP_VALUE_SET = 3
CMD_PROP_VALUE_INSERT = 4
CMD_PROP_VALUE_REMOVE = 5
#=========================================
# Spinel Command Responses: NCP -> Host
#=========================================
RSP_PROP_VALUE_IS = 6
RSP_PROP_VALUE_INSERTED = 7
RSP_PROP_VALUE_REMOVED = 8
CMD_NET_SAVE = 9
CMD_NET_CLEAR = 10
CMD_NET_RECALL = 11
RSP_HBO_OFFLOAD = 12
RSP_HBO_RECLAIM = 13
RSP_HBO_DROP = 14
CMD_HBO_OFFLOADED = 15
CMD_HBO_RECLAIMED = 16
CMD_HBO_DROPPED = 17
CMD_NEST__BEGIN = 15296
CMD_NEST__END = 15360
CMD_VENDOR__BEGIN = 15360
CMD_VENDOR__END = 16384
CMD_EXPERIMENTAL__BEGIN = 2000000
CMD_EXPERIMENTAL__END = 2097152
#=========================================
# Spinel Properties
#=========================================
PROP_LAST_STATUS = 0 # < status [i]
PROP_PROTOCOL_VERSION = 1 # < major, minor [i,i]
PROP_NCP_VERSION = 2 # < version string [U]
PROP_INTERFACE_TYPE = 3 # < [i]
PROP_VENDOR_ID = 4 # < [i]
PROP_CAPS = 5 # < capability list [A(i)]
PROP_INTERFACE_COUNT = 6 # < Interface count [C]
PROP_POWER_STATE = 7 # < PowerState [C]
PROP_HWADDR = 8 # < PermEUI64 [E]
PROP_LOCK = 9 # < PropLock [b]
PROP_HBO_MEM_MAX = 10 # < Max offload mem [S]
PROP_HBO_BLOCK_MAX = 11 # < Max offload block [S]
PROP_PHY__BEGIN = 0x20
PROP_PHY_ENABLED = PROP_PHY__BEGIN + 0 # < [b]
PROP_PHY_CHAN = PROP_PHY__BEGIN + 1 # < [C]
PROP_PHY_CHAN_SUPPORTED = PROP_PHY__BEGIN + 2 # < [A(C)]
PROP_PHY_FREQ = PROP_PHY__BEGIN + 3 # < kHz [L]
PROP_PHY_CCA_THRESHOLD = PROP_PHY__BEGIN + 4 # < dBm [c]
PROP_PHY_TX_POWER = PROP_PHY__BEGIN + 5 # < [c]
PROP_PHY_RSSI = PROP_PHY__BEGIN + 6 # < dBm [c]
PROP_PHY__END = 0x30
PROP_MAC__BEGIN = 0x30
PROP_MAC_SCAN_STATE = PROP_MAC__BEGIN + 0 # < [C]
PROP_MAC_SCAN_MASK = PROP_MAC__BEGIN + 1 # < [A(C)]
PROP_MAC_SCAN_PERIOD = PROP_MAC__BEGIN + 2 # < ms-per-channel [S]
# < chan,rssi,(laddr,saddr,panid,lqi),(proto,xtra) [CcT(ESSC.)T(i).]
PROP_MAC_SCAN_BEACON = PROP_MAC__BEGIN + 3
PROP_MAC_15_4_LADDR = PROP_MAC__BEGIN + 4 # < [E]
PROP_MAC_15_4_SADDR = PROP_MAC__BEGIN + 5 # < [S]
PROP_MAC_15_4_PANID = PROP_MAC__BEGIN + 6 # < [S]
PROP_MAC_RAW_STREAM_ENABLED = PROP_MAC__BEGIN + 7 # < [C]
PROP_MAC_FILTER_MODE = PROP_MAC__BEGIN + 8 # < [C]
PROP_MAC__END = 0x40
PROP_MAC_EXT__BEGIN = 0x1300
# Format: `A(T(Ec))`
# * `E`: EUI64 address of node
# * `c`: Optional fixed RSSI. -127 means not set.
PROP_MAC_WHITELIST = PROP_MAC_EXT__BEGIN + 0
PROP_MAC_WHITELIST_ENABLED = PROP_MAC_EXT__BEGIN + 1 # < [b]
PROP_MAC_EXT__END = 0x1400
PROP_NET__BEGIN = 0x40
PROP_NET_SAVED = PROP_NET__BEGIN + 0 # < [b]
PROP_NET_IF_UP = PROP_NET__BEGIN + 1 # < [b]
PROP_NET_STACK_UP = PROP_NET__BEGIN + 2 # < [C]
PROP_NET_ROLE = PROP_NET__BEGIN + 3 # < [C]
PROP_NET_NETWORK_NAME = PROP_NET__BEGIN + 4 # < [U]
PROP_NET_XPANID = PROP_NET__BEGIN + 5 # < [D]
PROP_NET_MASTER_KEY = PROP_NET__BEGIN + 6 # < [D]
PROP_NET_KEY_SEQUENCE = PROP_NET__BEGIN + 7 # < [L]
PROP_NET_PARTITION_ID = PROP_NET__BEGIN + 8 # < [L]
PROP_NET__END = 0x50
PROP_THREAD__BEGIN = 0x50
PROP_THREAD_LEADER_ADDR = PROP_THREAD__BEGIN + 0 # < [6]
PROP_THREAD_PARENT = PROP_THREAD__BEGIN + 1 # < LADDR, SADDR [ES]
PROP_THREAD_CHILD_TABLE = PROP_THREAD__BEGIN + 2 # < [A(T(ES))]
PROP_THREAD_LEADER_RID = PROP_THREAD__BEGIN + 3 # < [C]
PROP_THREAD_LEADER_WEIGHT = PROP_THREAD__BEGIN + 4 # < [C]
PROP_THREAD_LOCAL_LEADER_WEIGHT = PROP_THREAD__BEGIN + 5 # < [C]
PROP_THREAD_NETWORK_DATA = PROP_THREAD__BEGIN + 6 # < [D]
PROP_THREAD_NETWORK_DATA_VERSION = PROP_THREAD__BEGIN + 7 # < [S]
PROP_THREAD_STABLE_NETWORK_DATA = PROP_THREAD__BEGIN + 8 # < [D]
PROP_THREAD_STABLE_NETWORK_DATA_VERSION = PROP_THREAD__BEGIN + 9 # < [S]
# < array(ipv6prefix,prefixlen,stable,flags) [A(T(6CbC))]
PROP_THREAD_ON_MESH_NETS = PROP_THREAD__BEGIN + 10
# < array(ipv6prefix,prefixlen,stable,flags) [A(T(6CbC))]
PROP_THREAD_LOCAL_ROUTES = PROP_THREAD__BEGIN + 11
PROP_THREAD_ASSISTING_PORTS = PROP_THREAD__BEGIN + 12 # < array(portn) [A(S)]
PROP_THREAD_ALLOW_LOCAL_NET_DATA_CHANGE = PROP_THREAD__BEGIN + 13 # < [b]
PROP_THREAD_MODE = PROP_THREAD__BEGIN + 14
PROP_THREAD__END = 0x60
PROP_THREAD_EXT__BEGIN = 0x1500
PROP_THREAD_CHILD_TIMEOUT = PROP_THREAD_EXT__BEGIN + 0 # < [L]
PROP_THREAD_RLOC16 = PROP_THREAD_EXT__BEGIN + 1 # < [S]
PROP_THREAD_ROUTER_UPGRADE_THRESHOLD = PROP_THREAD_EXT__BEGIN + 2 # < [C]
PROP_THREAD_CONTEXT_REUSE_DELAY = PROP_THREAD_EXT__BEGIN + 3 # < [L]
PROP_THREAD_NETWORK_ID_TIMEOUT = PROP_THREAD_EXT__BEGIN + 4 # < [b]
PROP_THREAD_ACTIVE_ROUTER_IDS = PROP_THREAD_EXT__BEGIN + 5 # < [A(b)]
PROP_THREAD_RLOC16_DEBUG_PASSTHRU = PROP_THREAD_EXT__BEGIN + 6 # < [b]
PROP_THREAD_ROUTER_ROLE_ENABLED = PROP_THREAD_EXT__BEGIN + 7 # < [b]
PROP_THREAD_ROUTER_DOWNGRADE_THRESHOLD = PROP_THREAD_EXT__BEGIN + 8 # < [C]
PROP_THREAD_ROUTER_SELECTION_JITTER = PROP_THREAD_EXT__BEGIN + 9 # < [C]
PROP_THREAD_EXT__END = 0x1600
PROP_MESHCOP_EXT__BEGIN = 0x1600
PROP_MESHCOP_JOINER_ENABLE = PROP_MESHCOP_EXT__BEGIN + 0 # < [b]
PROP_MESHCOP_JOINER_CREDENTIAL = PROP_MESHCOP_EXT__BEGIN + 1 # < [D]
PROP_MESHCOP_JOINER_URL = PROP_MESHCOP_EXT__BEGIN + 2 # < [U]
PROP_MESHCOP_BORDER_AGENT_ENABLE = PROP_MESHCOP_EXT__BEGIN + 3 # < [b]
PROP_MESHCOP_EXT__END = 0x1700
PROP_IPV6__BEGIN = 0x60
PROP_IPV6_LL_ADDR = PROP_IPV6__BEGIN + 0 # < [6]
PROP_IPV6_ML_ADDR = PROP_IPV6__BEGIN + 1 # < [6C]
PROP_IPV6_ML_PREFIX = PROP_IPV6__BEGIN + 2 # < [6C]
# < array(ipv6addr,prefixlen,valid,preferred,flags) [A(T(6CLLC))]
PROP_IPV6_ADDRESS_TABLE = PROP_IPV6__BEGIN + 3
# < array(ipv6prefix,prefixlen,iface,flags) [A(T(6CCC))]
PROP_IPV6_ROUTE_TABLE = PROP_IPV6__BEGIN + 4
PROP_IPv6_ICMP_PING_OFFLOAD = PROP_IPV6__BEGIN + 5 # < [b]
PROP_STREAM__BEGIN = 0x70
PROP_STREAM_DEBUG = PROP_STREAM__BEGIN + 0 # < [U]
PROP_STREAM_RAW = PROP_STREAM__BEGIN + 1 # < [D]
PROP_STREAM_NET = PROP_STREAM__BEGIN + 2 # < [D]
PROP_STREAM_NET_INSECURE = PROP_STREAM__BEGIN + 3 # < [D]
PROP_STREAM__END = 0x80
# UART Bitrate
# Format: `L`
PROP_UART_BITRATE = 0x100
# UART Software Flow Control
# Format: `b`
PROP_UART_XON_XOFF = 0x101
PROP_PIB_15_4__BEGIN = 1024
PROP_PIB_15_4_PHY_CHANNELS_SUPPORTED = PROP_PIB_15_4__BEGIN + 0x01 # < [A(L)]
PROP_PIB_15_4_MAC_PROMISCUOUS_MODE = PROP_PIB_15_4__BEGIN + 0x51 # < [b]
PROP_PIB_15_4_MAC_SECURITY_ENABLED = PROP_PIB_15_4__BEGIN + 0x5d # < [b]
PROP_PIB_15_4__END = 1280
PROP_CNTR__BEGIN = 1280
# Counter reset behavior
# Format: `C`
PROP_CNTR_RESET = PROP_CNTR__BEGIN + 0
# The total number of transmissions.
# Format: `L` (Read-only) */
PROP_CNTR_TX_PKT_TOTAL = PROP_CNTR__BEGIN + 1
# The number of transmissions with ack request.
# Format: `L` (Read-only) */
PROP_CNTR_TX_PKT_ACK_REQ = PROP_CNTR__BEGIN + 2
# The number of transmissions that were acked.
# Format: `L` (Read-only) */
PROP_CNTR_TX_PKT_ACKED = PROP_CNTR__BEGIN + 3
# The number of transmissions without ack request.
# Format: `L` (Read-only) */
PROP_CNTR_TX_PKT_NO_ACK_REQ = PROP_CNTR__BEGIN + 4
# The number of transmitted data.
# Format: `L` (Read-only) */
PROP_CNTR_TX_PKT_DATA = PROP_CNTR__BEGIN + 5
# The number of transmitted data poll.
# Format: `L` (Read-only) */
PROP_CNTR_TX_PKT_DATA_POLL = PROP_CNTR__BEGIN + 6
# The number of transmitted beacon.
# Format: `L` (Read-only) */
PROP_CNTR_TX_PKT_BEACON = PROP_CNTR__BEGIN + 7
# The number of transmitted beacon request.
# Format: `L` (Read-only) */
PROP_CNTR_TX_PKT_BEACON_REQ = PROP_CNTR__BEGIN + 8
# The number of transmitted other types of frames.
# Format: `L` (Read-only) */
PROP_CNTR_TX_PKT_OTHER = PROP_CNTR__BEGIN + 9
# The number of retransmission times.
# Format: `L` (Read-only) */
PROP_CNTR_TX_PKT_RETRY = PROP_CNTR__BEGIN + 10
# The number of CCA failure times.
# Format: `L` (Read-only) */
PROP_CNTR_TX_ERR_CCA = PROP_CNTR__BEGIN + 11
# The total number of received packets.
# Format: `L` (Read-only) */
PROP_CNTR_RX_PKT_TOTAL = PROP_CNTR__BEGIN + 100
# The number of received data.
# Format: `L` (Read-only) */
PROP_CNTR_RX_PKT_DATA = PROP_CNTR__BEGIN + 101
# The number of received data poll.
# Format: `L` (Read-only) */
PROP_CNTR_RX_PKT_DATA_POLL = PROP_CNTR__BEGIN + 102
# The number of received beacon.
# Format: `L` (Read-only) */
PROP_CNTR_RX_PKT_BEACON = PROP_CNTR__BEGIN + 103
# The number of received beacon request.
# Format: `L` (Read-only) */
PROP_CNTR_RX_PKT_BEACON_REQ = PROP_CNTR__BEGIN + 104
# The number of received other types of frames.
# Format: `L` (Read-only) */
PROP_CNTR_RX_PKT_OTHER = PROP_CNTR__BEGIN + 105
# The number of received packets filtered by whitelist.
# Format: `L` (Read-only) */
PROP_CNTR_RX_PKT_FILT_WL = PROP_CNTR__BEGIN + 106
# The number of received packets filtered by destination check.
# Format: `L` (Read-only) */
PROP_CNTR_RX_PKT_FILT_DA = PROP_CNTR__BEGIN + 107
# The number of received packets that are empty.
# Format: `L` (Read-only) */
PROP_CNTR_RX_ERR_EMPTY = PROP_CNTR__BEGIN + 108
# The number of received packets from an unknown neighbor.
# Format: `L` (Read-only) */
PROP_CNTR_RX_ERR_UKWN_NBR = PROP_CNTR__BEGIN + 109
# The number of received packets whose source address is invalid.
# Format: `L` (Read-only) */
PROP_CNTR_RX_ERR_NVLD_SADDR = PROP_CNTR__BEGIN + 110
# The number of received packets with a security error.
# Format: `L` (Read-only) */
PROP_CNTR_RX_ERR_SECURITY = PROP_CNTR__BEGIN + 111
# The number of received packets with a checksum error.
# Format: `L` (Read-only) */
PROP_CNTR_RX_ERR_BAD_FCS = PROP_CNTR__BEGIN + 112
# The number of received packets with other errors.
# Format: `L` (Read-only) */
PROP_CNTR_RX_ERR_OTHER = PROP_CNTR__BEGIN + 113
#=========================================
MAC_FILTER_MDOE_NORMAL = 0
MAC_FILTER_MODE_PROMISCUOUS = 1
MAC_FILTER_MODE_MONITOR = 2
#=========================================
RSSI_OVERRIDE = 127
#=========================================
class kThread(object):
""" OpenThread constant class. """
PrefixPreferenceOffset = 6
PrefixPreferredFlag = 1 << 5
PrefixSlaacFlag = 1 << 4
PrefixDhcpFlag = 1 << 3
PrefixConfigureFlag = 1 << 2
PrefixDefaultRouteFlag = 1 << 1
PrefixOnMeshFlag = 1 << 0
#=========================================
SPINEL_LAST_STATUS_MAP = {
0: "STATUS_OK: Operation has completed successfully.",
1: "STATUS_FAILURE: Operation has failed for some undefined reason.",
2: "STATUS_UNIMPLEMENTED: The given operation has not been implemented.",
3: "STATUS_INVALID_ARGUMENT: An argument to the given operation is invalid.",
4: "STATUS_INVALID_STATE : The given operation is invalid for the current state of the device.",
5: "STATUS_INVALID_COMMAND: The given command is not recognized.",
6: "STATUS_INVALID_INTERFACE: The given Spinel interface is not supported.",
7: "STATUS_INTERNAL_ERROR: An internal runtime error has occured.",
8: "STATUS_SECURITY_ERROR: A security or authentication error has occured.",
9: "STATUS_PARSE_ERROR: An error has occured while parsing the command.",
10: "STATUS_IN_PROGRESS: The operation is in progress and will be completed asynchronously.",
11: "STATUS_NOMEM: The operation has been prevented due to memory pressure.",
12: "STATUS_BUSY: The device is currently performing a mutually exclusive operation.",
13: "STATUS_PROPERTY_NOT_FOUND: The given property is not recognized.",
14: "STATUS_PACKET_DROPPED: The packet was dropped.",
15: "STATUS_EMPTY: The result of the operation is empty.",
16: "STATUS_CMD_TOO_BIG: The command was too large to fit in the internal buffer.",
17: "STATUS_NO_ACK: The packet was not acknowledged.",
18: "STATUS_CCA_FAILURE: The packet was not sent due to a CCA failure.",
19: "SPINEL_STATUS_ALREADY: The operation is already in progress.",
20: "SPINEL_STATUS_ITEM_NOT_FOUND: The given item could not be found.",
104: "SPINEL_STATUS_JOIN_FAILURE",
105: "SPINEL_STATUS_JOIN_SECURITY: The network key has been set incorrectly.",
106: "SPINEL_STATUS_JOIN_NO_PEERS: The node was unable to find any other peers on the network.",
107: "SPINEL_STATUS_JOIN_INCOMPATIBLE: The only potential peer nodes found are incompatible.",
112: "STATUS_RESET_POWER_ON",
113: "STATUS_RESET_EXTERNAL",
114: "STATUS_RESET_SOFTWARE",
115: "STATUS_RESET_FAULT",
116: "STATUS_RESET_CRASH",
117: "STATUS_RESET_ASSERT",
118: "STATUS_RESET_OTHER",
119: "STATUS_RESET_UNKNOWN",
120: "STATUS_RESET_WATCHDOG",
0x4000: "kThreadError_None",
0x4001: "kThreadError_Failed",
0x4002: "kThreadError_Drop",
0x4003: "kThreadError_NoBufs",
0x4004: "kThreadError_NoRoute",
0x4005: "kThreadError_Busy",
0x4006: "kThreadError_Parse",
0x4007: "kThreadError_InvalidArgs",
0x4008: "kThreadError_Security",
0x4009: "kThreadError_AddressQuery",
0x400A: "kThreadError_NoAddress",
0x400B: "kThreadError_NotReceiving",
0x400C: "kThreadError_Abort",
0x400D: "kThreadError_NotImplemented",
0x400E: "kThreadError_InvalidState",
0x400F: "kThreadError_NoTasklets",
}
+161
View File
@@ -0,0 +1,161 @@
#
# 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.
#
""" High-Level Data Link Control (HDLC) module. """
import logging
from struct import pack
import spinel.config as CONFIG
from spinel.stream import IStream
from spinel.util import hexify_int
from spinel.util import hexify_bytes
HDLC_FLAG = 0x7e
HDLC_ESCAPE = 0x7d
# RFC 1662 Appendix C
HDLC_FCS_INIT = 0xFFFF
HDLC_FCS_POLY = 0x8408
HDLC_FCS_GOOD = 0xF0B8
class Hdlc(IStream):
""" Utility class for HDLC encoding and decoding. """
def __init__(self, stream):
self.stream = stream
self.fcstab = self.mkfcstab()
@classmethod
def mkfcstab(cls):
""" Make a static lookup table for byte value to FCS16 result. """
polynomial = HDLC_FCS_POLY
def valiter():
""" Helper to yield FCS16 table entries for each byte value. """
for byte in range(256):
fcs = byte
i = 8
while i:
fcs = (fcs >> 1) ^ polynomial if fcs & 1 else fcs >> 1
i -= 1
yield fcs & 0xFFFF
return tuple(valiter())
def fcs16(self, byte, fcs):
"""
Return the next iteration of an fcs16 calculation
given the next data byte and current fcs accumulator.
"""
fcs = (fcs >> 8) ^ self.fcstab[(fcs ^ byte) & 0xff]
return fcs
def collect(self):
""" Return the next valid packet to pass HDLC decoding on the stream. """
fcs = HDLC_FCS_INIT
packet = []
raw = []
# Synchronize
while 1:
byte = self.stream.read()
if CONFIG.DEBUG_HDLC:
raw.append(byte)
if byte == HDLC_FLAG:
break
# Read packet, updating fcs, and escaping bytes as needed
while 1:
byte = self.stream.read()
if CONFIG.DEBUG_HDLC:
raw.append(byte)
if byte == HDLC_FLAG:
break
if byte == HDLC_ESCAPE:
byte = self.stream.read()
if CONFIG.DEBUG_HDLC:
raw.append(byte)
byte ^= 0x20
packet.append(byte)
fcs = self.fcs16(byte, fcs)
if CONFIG.DEBUG_HDLC:
logging.debug("RX Hdlc: " + str(map(hexify_int, raw)))
if fcs != HDLC_FCS_GOOD:
packet = None
else:
packet = packet[:-2] # remove FCS16 from end
return packet
@classmethod
def encode_byte(cls, byte, packet=[]):
""" HDLC encode and append a single byte to the given packet. """
if (byte == HDLC_ESCAPE) or (byte == HDLC_FLAG):
packet.append(HDLC_ESCAPE)
packet.append(byte ^ 0x20)
else:
packet.append(byte)
return packet
def encode(self, payload=""):
""" Return the HDLC encoding of the given packet. """
fcs = HDLC_FCS_INIT
packet = []
packet.append(HDLC_FLAG)
for byte in payload:
byte = ord(byte)
fcs = self.fcs16(byte, fcs)
packet = self.encode_byte(byte, packet)
fcs ^= 0xffff
byte = fcs & 0xFF
packet = self.encode_byte(byte, packet)
byte = fcs >> 8
packet = self.encode_byte(byte, packet)
packet.append(HDLC_FLAG)
packet = pack("%dB" % len(packet), *packet)
if CONFIG.DEBUG_HDLC:
logging.debug("TX Hdlc: " + hexify_bytes(packet))
return packet
def write(self, data):
""" HDLC encode and write the given data to this stream. """
pkt = self.encode(data)
self.stream.write(pkt)
def read(self, _size=None):
""" Read and HDLC decode the next packet from this stream. """
pkt = self.collect()
return pkt
+63
View File
@@ -0,0 +1,63 @@
#
# 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 to provide codec utilities for .pcap formatters. """
import struct
from datetime import datetime
DLT_IEEE802_15_4 = 195
PCAP_MAGIC_NUMBER = 0xa1b2c3d4
PCAP_VERSION_MAJOR = 2
PCAP_VERSION_MINOR = 4
class PcapCodec(object):
""" Utility class for .pcap formatters. """
@classmethod
def encode_header(cls):
""" Returns a pcap file header. """
return struct.pack("<LHHLLLL",
PCAP_MAGIC_NUMBER,
PCAP_VERSION_MAJOR,
PCAP_VERSION_MINOR,
0, 0, 256,
DLT_IEEE802_15_4)
@classmethod
def encode_frame(cls, frame):
""" Returns a pcap encapsulation of the given frame. """
# write frame pcap header
epoch = datetime(1970, 1, 1)
d_time = datetime.utcnow() - epoch
sec = d_time.days * 24 * 60 * 60 + d_time.seconds
usec = d_time.microseconds
length = len(frame)
pcap_frame = struct.pack("<LLLL", sec, usec, length, length)
pcap_frame += frame
return pcap_frame
+172
View File
@@ -0,0 +1,172 @@
#!/usr/bin/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 generic stream interface.
Also includes adapter implementations for serial, socket, and pipes.
"""
import sys
import logging
import traceback
import subprocess
import socket
import serial
import spinel.util
import spinel.config as CONFIG
class IStream(object):
""" Abstract base class for a generic Stream Interface. """
def read(self, size):
""" Read an array of byte integers of the given size from the stream. """
pass
def write(self, data):
""" Write the given packed data to the stream. """
pass
def close(self):
""" Close the stream cleanly as needed. """
pass
class StreamSerial(IStream):
""" An IStream interface implementation for serial devices. """
def __init__(self, dev, baudrate=115200):
try:
self.serial = serial.Serial(dev, baudrate)
except:
logging.error("Couldn't open " + dev)
print traceback.format_exc()
def write(self, data):
self.serial.write(data)
if CONFIG.DEBUG_STREAM_TX:
logging.debug("TX Raw: " + str(map(spinel.util.hexify_chr, data)))
def read(self, size=1):
pkt = self.serial.read(size)
if CONFIG.DEBUG_STREAM_RX:
logging.debug("RX Raw: " + str(map(spinel.util.hexify_chr, pkt)))
return map(ord, pkt)[0]
class StreamSocket(IStream):
""" An IStream interface implementation over an internet socket. """
def __init__(self, hostname, port):
# Open socket
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.connect((hostname, port))
def write(self, data):
self.sock.send(data)
if CONFIG.DEBUG_STREAM_TX:
logging.debug("TX Raw: " + str(map(spinel.util.hexify_chr, data)))
def read(self, size=1):
pkt = self.sock.recv(size)
if CONFIG.DEBUG_STREAM_RX:
logging.debug("RX Raw: " + str(map(spinel.util.hexify_chr, pkt)))
return map(ord, pkt)[0]
class StreamPipe(IStream):
""" An IStream interface implementation to stdin/out of a piped process. """
def __init__(self, filename):
""" Create a stream object from a piped system call """
try:
self.pipe = subprocess.Popen(filename, shell=True,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=sys.stdout.fileno())
except:
logging.error("Couldn't open " + filename)
print traceback.format_exc()
def write(self, data):
if CONFIG.DEBUG_STREAM_TX:
logging.debug("TX Raw: (%d) %s",
len(data), spinel.util.hexify_bytes(data))
self.pipe.stdin.write(data)
def read(self, size=1):
""" Blocking read on stream object """
pkt = self.pipe.stdout.read(size)
if CONFIG.DEBUG_STREAM_RX:
logging.debug("RX Raw: " + str(map(spinel.util.hexify_chr, pkt)))
return map(ord, pkt)[0]
def close(self):
if self.pipe:
self.pipe.kill()
self.pipe = None
def StreamOpen(stream_type, descriptor, verbose=True):
"""
Factory function that creates and opens a stream connection.
stream_type:
'u' = uart (/dev/tty#)
's' = socket (port #)
'p' = pipe (stdin/stdout)
descriptor:
uart - filename of device (/dev/tty#)
socket - port to open connection to on localhost
pipe - filename of command to execute and bind via stdin/stdout
"""
if stream_type == 'p':
if verbose:
print "Opening pipe to " + str(descriptor)
return StreamPipe(descriptor)
elif stream_type == 's':
port = int(descriptor)
hostname = "localhost"
if verbose:
print "Opening socket to " + hostname + ":" + str(port)
return StreamSocket(hostname, port)
elif stream_type == 'u':
dev = str(descriptor)
baudrate = 115200
if verbose:
print "Opening serial to " + dev + " @ " + str(baudrate)
return StreamSerial(dev, baudrate)
else:
return None
+71
View File
@@ -0,0 +1,71 @@
#
# 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.
#
""" Unittest for spinel.codec module. """
import unittest
from spinel.const import SPINEL
from spinel.codec import WpanApi
from spinel.test_stream import MockStream
class TestCodec(unittest.TestCase):
""" Unit TestCase class for spinel.codec.SpinelCodec class. """
# Tests parsing and format demuxing of various properties with canned
# values.
VECTOR = {
SPINEL.PROP_MAC_15_4_PANID: 65535,
SPINEL.PROP_NCP_VERSION: "OPENTHREAD",
SPINEL.PROP_NET_ROLE: 0,
SPINEL.PROP_NET_KEY_SEQUENCE: 5,
SPINEL.PROP_NET_NETWORK_NAME: "OpenThread",
SPINEL.PROP_THREAD_MODE: 0xF,
}
def test_prop_get(self):
""" Unit test of SpinelCodec.prop_get_value. """
mock_stream = MockStream({
# Request: Response
"810236": "810636ffff", # get panid = 65535
"810243": "81064300", # get state = detached
"81025e": "81065e0f", # mode = 0xF
"810202": "8106024f50454e54485245414400", # get version
"810247": "81064705000000", # get keysequence
"810244": "8106444f70656e54687265616400", # get networkname
})
nodeid = 1
use_hdlc = False
wpan_api = WpanApi(mock_stream, nodeid, use_hdlc)
for prop_id, truth_value in self.VECTOR.iteritems():
value = wpan_api.prop_get_value(prop_id)
# print "value "+util.hexify_str(value)
# print "truth "+util.hexify_str(truth_value)
self.failUnless(value == truth_value)
+58
View File
@@ -0,0 +1,58 @@
#
# 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.
#
"""
Unittest for spinel.hdlc module.
"""
import unittest
import binascii
from spinel.hdlc import Hdlc
class TestHdlc(unittest.TestCase):
""" Unittest class for spinel.hdlc.Hdlc class. """
VECTOR = {
# Data HDLC Encoded
"810243": "7e810243d3d37e",
"8103367e7d": "7e8103367d5e7d5d6af97e",
}
def test_hdlc_encode(self):
""" Unit test for Hdle.encode method. """
hdlc = Hdlc(None)
for in_hex, out_hex in self.VECTOR.iteritems():
in_binary = binascii.unhexlify(in_hex)
out_binary = hdlc.encode(in_binary)
#print "inHex = "+binascii.hexlify(in_binary)
#print "outHex = "+binascii.hexlify(out_binary)
self.failUnless(out_hex == binascii.hexlify(out_binary))
def test_hdlc_decode(self):
""" Unit test for Hdle.decode method. """
pass
+66
View File
@@ -0,0 +1,66 @@
#
# 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.
#
""" Unittest for spinel.codec module. """
import unittest
import spinel.util as util
from spinel.const import SPINEL
from spinel.codec import WpanApi
from spinel.test_stream import MockStream
class TestSniffer(unittest.TestCase):
""" Unit TestCase class for sniffer relevant portions of spinel.codec.SpinelCodec. """
HEADER = "800671" # CMD_PROP_IS RAW_STREAM
VECTOR = [
# Some raw 6lo packets: ICMPv6EchoRequest to ff02::1, fe80::1, and MLE Advertisement
"2d00499880fffffffffeff0d0100000001a7acdf3be9272c2d88765ff76f0bf08a7c3df0a78e9c1b23eb019c58740300800000",
"3200699c81ffff0100000000000002feff0d030000000198e80cac00f8e0754e7542f5cb1171069f5c9689ef8d1d45a75e26b3f600800000",
"450041d8980100ffffa8cb25ab2c32a0227f3b01f04d4c4d4cdc3b0015060000000000000001f226cce17968521d92904fec1adb0b94777030b944df65450bc955f05737e3901700800000"
]
def test_prop_get(self):
""" Unit test of SpinelCodec.prop_get_value. """
mock_stream = MockStream({})
nodeid = 1
use_hdlc = False
tid = SPINEL.HEADER_ASYNC
prop_id = SPINEL.PROP_STREAM_RAW
wpan_api = WpanApi(mock_stream, nodeid, use_hdlc)
wpan_api.queue_register(tid)
for truth in self.VECTOR:
mock_stream.write_child_hex(self.HEADER+truth)
result = wpan_api.queue_wait_for_prop(prop_id, tid)
packet = util.hexify_str(result.value,"")
self.failUnless(packet == truth)
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/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.
#
"""
Tests for spinel.stream and implementation of MockStream class.
"""
import binascii
import logging
import Queue
import spinel.util as util
import spinel.config as CONFIG
from spinel.stream import IStream
class MockStream(IStream):
""" A pluggable IStream class for mock testing input/output data flows. """
def __init__(self, vector):
"""
Pass a test vector as dictionary of hexstream outputs keyed on inputs.
"""
self.vector = vector
self.rx_queue = Queue.Queue()
self.response = None
def write(self, out_binary):
""" Write to the MockStream, triggering a lookup for mock response. """
if CONFIG.DEBUG_STREAM_TX:
logging.debug("TX Raw: (%d) %s", len(out_binary),
util.hexify_bytes(out_binary))
out_hex = binascii.hexlify(out_binary)
in_hex = self.vector[out_hex]
self.rx_queue.put_nowait(binascii.unhexlify(in_hex))
def read(self, size=None):
""" Blocking read from the MockStream. """
if not self.response or len(self.response) == 0:
self.response = self.rx_queue.get(True)
if size:
in_binary = self.response[:size]
self.response = self.response[size:]
else:
in_binary = self.response
self.response = None
if CONFIG.DEBUG_STREAM_RX:
logging.debug("RX Raw: " + util.hexify_bytes(in_binary))
return in_binary
def write_child(self, out_binary):
""" Mock asynchronous write from child process. """
self.rx_queue.put_nowait(out_binary)
def write_child_hex(self, out_hex):
""" Mock asynchronous write from child process. """
self.write_child(binascii.unhexlify(out_hex))
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/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.
#
from spinel.test_hdlc import TestHdlc
from spinel.test_codec import TestCodec
from spinel.test_sniffer import TestSniffer
+153
View File
@@ -0,0 +1,153 @@
#
# 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.
#
""" Utility class for creating TUN network interfaces on Linux and OSX. """
import os
import sys
import fcntl
import struct
import logging
import threading
import traceback
import subprocess
from select import select
import spinel.util as util
import spinel.config as CONFIG
IFF_TUN = 0x0001
IFF_TAP = 0x0002
IFF_NO_PI = 0x1000
IFF_TUNSETIFF = 0x400454ca
IFF_TUNSETOWNER = IFF_TUNSETIFF + 2
class TunInterface(object):
""" Utility class for creating a TUN network interface. """
def __init__(self, identifier):
self.identifier = identifier
self.ifname = "tun" + str(self.identifier)
self.tun = None
self.fd = None
platform = sys.platform
if platform == "linux" or platform == "linux2":
self.__init_linux()
elif platform == "darwin":
self.__init_osx()
self.ifconfig("up")
#self.ifconfig("inet6 add fd00::1/64")
self.__start_tun_thread()
def __init_osx(self):
logging.info("TUN: Starting osx " + self.ifname)
filename = "/dev/" + self.ifname
self.tun = os.open(filename, os.O_RDWR)
self.fd = self.tun
# trick osx to auto-assign a link local address
self.addr_add("fe80::1")
self.addr_del("fe80::1")
def __init_linux(self):
logging.info("TUN: Starting linux " + self.ifname)
self.tun = open("/dev/net/tun", "r+b")
self.fd = self.tun.fileno()
ifr = struct.pack("16sH", self.ifname, IFF_TUN | IFF_NO_PI)
fcntl.ioctl(self.tun, IFF_TUNSETIFF, ifr) # Name interface tun#
fcntl.ioctl(self.tun, IFF_TUNSETOWNER, 1000) # Allow non-sudo access
def close(self):
""" Close this tunnel interface. """
if self.tun:
os.close(self.fd)
self.fd = None
self.tun = None
@classmethod
def command(cls, cmd):
""" Utility to make a system call. """
subprocess.check_call(cmd, shell=True)
def ifconfig(self, args):
""" Bring interface up and/or assign addresses. """
self.command('ifconfig ' + self.ifname + ' ' + args)
def ping6(self, args):
""" Ping an address. """
cmd = 'ping6 ' + args
print cmd
self.command(cmd)
def addr_add(self, addr):
""" Add the given IPv6 address to the tunnel interface. """
self.ifconfig('inet6 add ' + addr)
def addr_del(self, addr):
""" Delete the given IPv6 address from the tunnel interface. """
platform = sys.platform
if platform == "linux" or platform == "linux2":
self.ifconfig('inet6 del ' + addr)
elif platform == "darwin":
self.ifconfig('inet6 delete ' + addr)
def write(self, packet):
#global gWpanApi
#gWpanApi.ip_send(packet)
# os.write(self.fd, packet) # Loop back
if CONFIG.DEBUG_TUN:
logging.debug("\nTUN: TX (" + str(len(packet)) +
") " + util.hexify_str(packet))
def __run_tun_thread(self):
while self.fd:
try:
ready_fd = select([self.fd], [], [])[0][0]
if ready_fd == self.fd:
packet = os.read(self.fd, 4000)
if CONFIG.DEBUG_TUN:
logging.debug("\nTUN: RX (" + str(len(packet)) + ") " +
util.hexify_str(packet))
self.write(packet)
except:
print traceback.format_exc()
break
logging.info("TUN: exiting")
if self.fd:
os.close(self.fd)
self.fd = None
def __start_tun_thread(self):
"""Start reader thread"""
self._reader_alive = True
self.receiver_thread = threading.Thread(target=self.__run_tun_thread)
self.receiver_thread.setDaemon(True)
self.receiver_thread.start()
+48
View File
@@ -0,0 +1,48 @@
#!/usr/bin/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.
#
def hexify_chr(s): return "%02X" % ord(s)
def hexify_int(i): return "%02X" % i
def hexify_bytes(data): return str(map(hexify_chr,data))
def hexify_str(s,delim=':'):
return delim.join(x.encode('hex') for x in s)
def pack_bytes(packet): return pack("%dB" % len(packet), *packet)
def packed_to_array(packet): return map(ord, packet)
def asciify_int(i): return "%c" % (i)
def hex_to_bytes(s):
result = ''
for i in xrange(0, len(s), 2):
(b1, b2) = s[i:i+2]
hex = b1+b2
v = int(hex, 16)
result += chr(v)
return result
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/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.
#
""" Run all unittests for spinel module. """
import sys
import optparse
import unittest
import spinel.config as CONFIG
from spinel.tests import *
def main():
""" Run all unit tests for spinel module. """
args = sys.argv[1:]
opt_parser = optparse.OptionParser()
opt_parser.add_option("-d", "--debug", action="store",
dest="debug", type="int", default=CONFIG.DEBUG_ENABLE)
(options, remaining_args) = opt_parser.parse_args(args)
if options.debug:
CONFIG.debug_set_level(options.debug)
sys.argv[1:] = remaining_args
unittest.main()
if __name__ == '__main__':
main()