mirror of
https://github.com/espressif/openthread.git
synced 2026-07-30 23:57:47 +00:00
[tests] adding toranj test framework (#2559)
This commit adds the base implementation of `toranj`, a test framework for OpenThread and `wpantund`. - It enables testing of combined behavior of OpenThread (in NCP mode), spinel interface, and `wpantund` driver on linux. - It can be used to simulate multiple nodes forming complex network topologies, testing network interactions between many nodes (e.g., IPv6 traffic exchanges). This commit also sets up the new test-cases to run as part of travis pull request validation in OpenThread GitHub projects.
This commit is contained in:
committed by
Jonathan Hui
parent
c98164f933
commit
ef17e914a9
@@ -34,6 +34,11 @@ dist: trusty
|
||||
before_install:
|
||||
- .travis/before_install.sh
|
||||
|
||||
before_script:
|
||||
- if [ "${TRAVIS_OS_NAME}" == "linux" ]; then
|
||||
sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6';
|
||||
fi
|
||||
|
||||
script:
|
||||
- .travis/script.sh
|
||||
|
||||
@@ -118,3 +123,6 @@ matrix:
|
||||
- env: BUILD_TARGET="posix-ncp-spi" VERBOSE=1
|
||||
os: linux
|
||||
compiler: gcc
|
||||
- env: BUILD_TARGET="toranj-test-framework" VERBOSE=1
|
||||
os: linux
|
||||
compiler: gcc
|
||||
|
||||
@@ -97,6 +97,29 @@ cd /tmp || die
|
||||
[ $BUILD_TARGET != posix -o $CC != clang ] || {
|
||||
sudo apt-get install clang || die
|
||||
}
|
||||
|
||||
[ $BUILD_TARGET != toranj-test-framework ] || {
|
||||
pip install --upgrade pip || die
|
||||
|
||||
# packages for wpantund
|
||||
sudo apt-get install dbus || die
|
||||
sudo apt-get install gcc g++ libdbus-1-dev || die
|
||||
sudo apt-get install autoconf-archive || die
|
||||
sudo apt-get install bsdtar || die
|
||||
sudo apt-get install libtool || die
|
||||
sudo apt-get install libglib2.0-dev || die
|
||||
sudo apt-get install libboost-dev || die
|
||||
sudo apt-get install libboost-signals-dev || die
|
||||
|
||||
# clone and build wpantund
|
||||
git clone --depth=1 --branch=master https://github.com/openthread/wpantund.git
|
||||
cd wpantund || die
|
||||
./bootstrap.sh || die
|
||||
./configure || die
|
||||
sudo make install -j 8 || die
|
||||
cd .. || die
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[ $TRAVIS_OS_NAME != osx ] || {
|
||||
|
||||
@@ -370,3 +370,7 @@ set -x
|
||||
./bootstrap || die
|
||||
COVERAGE=1 NODE_TYPE=ncp-sim make -f examples/Makefile-posix check || die
|
||||
}
|
||||
|
||||
[ $BUILD_TARGET != toranj-test-framework ] || {
|
||||
./tests/toranj/start.sh
|
||||
}
|
||||
|
||||
@@ -0,0 +1,369 @@
|
||||
# `toranj` test framework
|
||||
|
||||
`toranj` is a test framework for OpenThread and `wpantund`.
|
||||
|
||||
- It enables testing of combined behavior of OpenThread (in NCP mode), spinel interface, and `wpantund` driver on linux.
|
||||
- It can be used to simulate multiple nodes forming complex network topologies.
|
||||
- It allows testing of network interactions between many nodes (IPv6 traffic exchanges).
|
||||
|
||||
`toranj` is developed in Python. `toranj` runs wpantund natively with OpenThread in NCP mode on POSIX simulation platform.
|
||||
`toranj` tests will run as part of travis pull request validation in OpenThread and/or `wpantund` GitHub projects.
|
||||
|
||||
|
||||
## Setup
|
||||
|
||||
`toranj` requires wpantund to be installed. Please follow [`wpantund` installation guide](https://github.com/openthread/wpantund/blob/master/INSTALL.md#wpantund-installation-guide).
|
||||
|
||||
To run all tests, `start` script can be used. This script will build OpenThread with proper configuration options and starts running all test.
|
||||
|
||||
```bash
|
||||
cd tests/toranj/ # from OpenThread repo root
|
||||
./start.sh
|
||||
```
|
||||
|
||||
Each test-case has its own script following naming model `test-nnn-name.py` (e.g., `test-001-get-set.py`).
|
||||
|
||||
To run a specific test
|
||||
|
||||
```bash
|
||||
sudo python test-001-get-set.py
|
||||
```
|
||||
|
||||
## `toranj` Components
|
||||
|
||||
`wpan` python module defines the `toranj` test components.
|
||||
|
||||
### `wpan.Node()` Class
|
||||
|
||||
`wpan.Node()` class creates a Thread node instance. It creates a sub-process to run `wpantund` and OpenThread, and provides methods to control the node.
|
||||
|
||||
|
||||
```python
|
||||
>>> import wpan
|
||||
>>> node1 = wpan.Node()
|
||||
>>> node1
|
||||
Node (index=1, interface_name=wpan1)
|
||||
>>> node2 = wpan.Node()
|
||||
>>> node2
|
||||
Node (index=2, interface_name=wpan2)
|
||||
```
|
||||
Note: You may need to run as `sudo` to allow `wpantund` to create tunnel interface (i.e., use `sudo python`).
|
||||
|
||||
### `wpan.Node` methods providing `wpanctl` commands
|
||||
|
||||
`wpan.Node()` provides methods matching all `wpanctl` commands.
|
||||
|
||||
- Get the value of a `wpantund` property, set the value, or add/remove value to/from a list based property:
|
||||
|
||||
```python
|
||||
node.get(prop_name)
|
||||
node.set(prop_name, value, binary_data=False)
|
||||
node.add(prop_name, value, binary_data=False)
|
||||
node.remove(prop_name, value, binary_data=False)
|
||||
```
|
||||
|
||||
Example:
|
||||
```python
|
||||
>>> node.get(wpan.WPAN_NAME)
|
||||
'"test-network"'
|
||||
>>> node.set(wpan.WPAN_NAME, 'my-network')
|
||||
>>> node.get(wpan.WPAN_NAME)
|
||||
'"my-network"'
|
||||
>>> node.set(wpan.WPAN_KEY, '65F2C35C7B543BAC1F3E26BB9F866C1D', binary_data=True)
|
||||
>>> node.get(wpan.WPAN_KEY)
|
||||
'[65F2C35C7B543BAC1F3E26BB9F866C1D]'
|
||||
```
|
||||
|
||||
- Common network operations:
|
||||
```python
|
||||
node.reset() # Reset the NCP
|
||||
node.status() # Get current status
|
||||
node.leave() # Leave the current network, clear all persistent data
|
||||
|
||||
# Form a network in given channel (if none given use a random one)
|
||||
node.form(name, channel=None)
|
||||
|
||||
# Join a network with given info.
|
||||
# node_type can be JOIN_TYPE_ROUTER, JOIN_TYPE_END_DEVICE, JOIN_TYPE_SLEEPY_END_DEVICE
|
||||
node.join(name, channel=None, node_type=None, panid=None, xpanid=None)
|
||||
```
|
||||
|
||||
Example:
|
||||
```python
|
||||
>>> result = node.status()
|
||||
>>> print result
|
||||
wpan1 => [
|
||||
"NCP:State" => "offline"
|
||||
"Daemon:Enabled" => true
|
||||
"NCP:Version" => "OPENTHREAD/20170716-00460-ga438cef0c-dirty; NONE; Feb 12 2018 11:47:01"
|
||||
"Daemon:Version" => "0.08.00d (0.07.01-191-g63265f7; Feb 2 2018 18:05:47)"
|
||||
"Config:NCP:DriverName" => "spinel"
|
||||
"NCP:HardwareAddress" => [18B4300000000001]
|
||||
]
|
||||
>>>
|
||||
>>> node.form("test-network", channel=12)
|
||||
'Forming WPAN "test-network" as node type "router"\nSuccessfully formed!'
|
||||
>>>
|
||||
>>> print node.status()
|
||||
wpan1 => [
|
||||
"NCP:State" => "associated"
|
||||
"Daemon:Enabled" => true
|
||||
"NCP:Version" => "OPENTHREAD/20170716-00460-ga438cef0c-dirty; NONE; Feb 12 2018 11:47:01"
|
||||
"Daemon:Version" => "0.08.00d (0.07.01-191-g63265f7; Feb 2 2018 18:05:47)"
|
||||
"Config:NCP:DriverName" => "spinel"
|
||||
"NCP:HardwareAddress" => [18B4300000000001]
|
||||
"NCP:Channel" => 12
|
||||
"Network:NodeType" => "leader"
|
||||
"Network:Name" => "test-network"
|
||||
"Network:XPANID" => 0xA438CF5973FD86B2
|
||||
"Network:PANID" => 0x9D81
|
||||
"IPv6:MeshLocalAddress" => "fda4:38cf:5973:0:b899:3436:15c6:941d"
|
||||
"IPv6:MeshLocalPrefix" => "fda4:38cf:5973::/64"
|
||||
"com.nestlabs.internal:Network:AllowingJoin" => false
|
||||
]
|
||||
```
|
||||
|
||||
- Scan:
|
||||
```python
|
||||
node.active_scan(channel=None)
|
||||
node.energy_scan(channel=None)
|
||||
node.discover_scan(channel=None, joiner_only=False, enable_filtering=False, panid_filter=None)
|
||||
node.permit_join(duration_sec=None, port=None, udp=True, tcp=True)
|
||||
```
|
||||
|
||||
- On-mesh prefixes and off-mesh routes:
|
||||
```python
|
||||
node.config_gateway(prefix, default_route=False)
|
||||
node.add_route(route_prefix, prefix_len_in_bytes=None, priority=None)
|
||||
node.remove_route(route_prefix, prefix_len_in_bytes=None, priority=None)
|
||||
```
|
||||
|
||||
A direct `wpanctl` command can be issued using `node.wpanctl(command)` with a given `command` string.
|
||||
|
||||
`wpan` module provides variables for different `wpantund` properties. Some commonly used are:
|
||||
|
||||
- Network/NCP properties:
|
||||
WPAN_STATE, WPAN_NAME, WPAN_PANID, WPAN_XPANID, WPAN_KEY, WPAN_CHANNEL, WPAN_HW_ADDRESS,
|
||||
WPAN_EXT_ADDRESS, WPAN_POLL_INTERVAL, WPAN_NODE_TYPE, WPAN_ROLE, WPAN_PARTITION_ID
|
||||
|
||||
- IPv6 Addresses:
|
||||
WPAN_IP6_LINK_LOCAL_ADDRESS, WPAN_IP6_MESH_LOCAL_ADDRESS, WPAN_IP6_MESH_LOCAL_PREFIX,
|
||||
WPAN_IP6_ALL_ADDRESSES, WPAN_IP6_MULTICAST_ADDRESSES
|
||||
|
||||
- Thread Properties:
|
||||
WPAN_THREAD_RLOC16, WPAN_THREAD_ROUTER_ID, WPAN_THREAD_LEADER_ADDRESS,
|
||||
WPAN_THREAD_LEADER_ROUTER_ID, WPAN_THREAD_LEADER_WEIGHT, WPAN_THREAD_LEADER_NETWORK_DATA,
|
||||
|
||||
WPAN_THREAD_CHILD_TABLE, WPAN_THREAD_CHILD_TABLE_ADDRESSES, WPAN_THREAD_NEIGHBOR_TABLE,
|
||||
WPAN_THREAD_ROUTER_TABLE
|
||||
|
||||
|
||||
Method `join_node()` can be used by a node to join another node:
|
||||
|
||||
```python
|
||||
# `node1` joining `node2`'s network as a router
|
||||
node1.join_node(node2, node_type=JOIN_TYPE_ROUTER)
|
||||
```
|
||||
|
||||
Method `whitelist_node()` can be used to add a given node to the whitelist of the device and enables white-listing:
|
||||
|
||||
```python
|
||||
# `node2` is added to the whitelist of `node1` and white-listing is enabled on `node1`
|
||||
node1.whitelist_node(node2)
|
||||
```
|
||||
|
||||
#### Example (simple 3-node topology)
|
||||
|
||||
Script below shows how to create a 3-node network topology with `node1` and `node2` being routers, and `node3` an end-device connected to `node2`:
|
||||
```python
|
||||
>>> import wpan
|
||||
>>> node1 = wpan.Node()
|
||||
>>> node2 = wpan.Node()
|
||||
>>> node3 = wpan.Node()
|
||||
|
||||
>>> wpan.Node.init_all_nodes()
|
||||
|
||||
>>> node1.form("test-PAN")
|
||||
'Forming WPAN "test-PAN" as node type "router"\nSuccessfully formed!'
|
||||
|
||||
>>> node1.whitelist_node(node2)
|
||||
>>> node2.whitelist_node(node1)
|
||||
|
||||
>>> node2.join_node(node1, wpan.JOIN_TYPE_ROUTER)
|
||||
'Joining "test-PAN" C474513CB487778D as node type "router"\nSuccessfully Joined!'
|
||||
|
||||
>>> node3.whitelist_node(node2)
|
||||
>>> node2.whitelist_node(node3)
|
||||
|
||||
>>> node3.join_node(node2, wpan.JOIN_TYPE_END_DEVICE)
|
||||
'Joining "test-PAN" C474513CB487778D as node type "end-device"\nSuccessfully Joined!'
|
||||
|
||||
>>> print node2.get(wpan.WPAN_THREAD_NEIGHBOR_TABLE)
|
||||
[
|
||||
"EAC1672C3EAB30A4, RLOC16:9401, LQIn:3, AveRssi:-20, LastRssi:-20, Age:30, LinkFC:6, MleFC:0, IsChild:yes, RxOnIdle:yes, FFD:yes, SecDataReq:yes, FullNetData:yes"
|
||||
"A2042C8762576FD5, RLOC16:dc00, LQIn:3, AveRssi:-20, LastRssi:-20, Age:5, LinkFC:21, MleFC:18, IsChild:no, RxOnIdle:yes, FFD:yes, SecDataReq:no, FullNetData:yes"
|
||||
]
|
||||
>>> print node1.get(wpan.WPAN_THREAD_NEIGHBOR_TABLE)
|
||||
[
|
||||
"960947C53415DAA1, RLOC16:9400, LQIn:3, AveRssi:-20, LastRssi:-20, Age:18, LinkFC:15, MleFC:11, IsChild:no, RxOnIdle:yes, FFD:yes, SecDataReq:no, FullNetData:yes"
|
||||
]
|
||||
|
||||
```
|
||||
|
||||
### IPv6 Message Exchange
|
||||
|
||||
`toranj` allows a test-case to define traffic patterns (IPv6 message exchange) between different nodes. Message exchanges (tx/rx) are prepared and then an async rx/tx operation starts. The success and failure of tx/rx operations can then be verified by the test case.
|
||||
|
||||
`wpan.Node` method `prepare_tx()` prepares a UDP6 transmission from a node.
|
||||
|
||||
```python
|
||||
node1.prepare_tx(src, dst, data, count)
|
||||
```
|
||||
|
||||
- `src` and `dst` can be
|
||||
- either a string containing an IPv6 address
|
||||
- or a tuple (ipv6 address as string, port). if no port is given, a random port number is used.
|
||||
|
||||
- `data` can be
|
||||
- either a string containing the message to be sent,
|
||||
- or an int indicating size of the message (a random message with the given length will be generated).
|
||||
|
||||
- `count` gives number of times the message will be sent (default is 1).
|
||||
|
||||
`prepare_tx` returns a `wpan.AsyncSender` object. The sender object can be used to check success/failure of tx operation.
|
||||
|
||||
`wpan.Node` method `prepare_rx()` prepares a node to listen for UDP messages from a sender.
|
||||
|
||||
```python
|
||||
node2.prepare_rx(sender)
|
||||
```
|
||||
|
||||
- `sender` should be an `wpan.AsyncSender` object returned from previous `prepare_tx`.
|
||||
- `prepare_rx()` returns a `wpan.AsyncReceiver` object to help test to check success/failure of rx operation.
|
||||
|
||||
After all exchanges are prepared, static method `perform_async_tx_rx()` should be used to start all previously prepared rx and tx operations.
|
||||
|
||||
```python
|
||||
wpan.Node.perform_async_tx_rx(timeout)
|
||||
```
|
||||
|
||||
- `timeout` gives amount of time (in seconds) to wait for all operations to finish. (default is 20 seconds)
|
||||
|
||||
After `perform_async_tx_rx()` is done, the `AsyncSender` and `AsyncReceiver` objects can check if operations were successful (using property `was_successful`)
|
||||
|
||||
#### Example
|
||||
|
||||
Sending 10 messages containing `"Hello there!"` from `node1` to `node2` using their mesh-local addresses:
|
||||
```python
|
||||
# `node1` and `node2` are already joined and are part of the same Thread network.
|
||||
|
||||
# Get the mesh local addresses
|
||||
>>> mladdr1 = node1.get(wpan.WPAN_IP6_MESH_LOCAL_ADDRESS)[1:-1] # remove `"` from start/end of string
|
||||
>>> mladdr2 = node2.get(wpan.WPAN_IP6_MESH_LOCAL_ADDRESS)[1:-1]
|
||||
|
||||
>>> print (mladdr1, mladdr2)
|
||||
('fda4:38cf:5973:0:b899:3436:15c6:941d', 'fda4:38cf:5973:0:5836:fa55:7394:6d4b')
|
||||
|
||||
# prepare a `sender` and corresponding `recver`
|
||||
>>> sender = node1.prepare_tx((mladdr1, 1234), (mladdr2, 2345), "Hello there!", 10)
|
||||
>>> recver = node2.prepare_rx(sender)
|
||||
|
||||
# perform async message transfer
|
||||
>>> wpan.Node.perform_async_tx_rx()
|
||||
|
||||
# check status of `sender` and `recver`
|
||||
>>> sender.was_successful
|
||||
True
|
||||
>>> recver.was_successful
|
||||
True
|
||||
|
||||
# `sender` or `recver` can provide info about the exchange
|
||||
|
||||
>>> sender.src_addr
|
||||
'fda4:38cf:5973:0:b899:3436:15c6:941d'
|
||||
>>> sender.src_port
|
||||
1234
|
||||
>>> sender.dst_addr
|
||||
'fda4:38cf:5973:0:5836:fa55:7394:6d4b'
|
||||
>>> sender.dst_port
|
||||
2345
|
||||
>>> sender.msg
|
||||
'Hello there!'
|
||||
>>> sender.count
|
||||
10
|
||||
|
||||
# get all received msg by `recver` as list of tuples `(msg, (src_address, src_port))`
|
||||
>>> recver.all_rx_msg
|
||||
[('Hello there!', ('fda4:38cf:5973:0:b899:3436:15c6:941d', 1234)), ... ]
|
||||
```
|
||||
|
||||
### Logs and Verbose mode
|
||||
|
||||
Every `wpan.Node()` instance will save its corresponding `wpantund` logs. By default the logs are saved in a file
|
||||
`wpantun-log<node_index>.log`. By setting `wpan.Node__TUND_LOG_TO_FILE` to `False` the logs are written to `stdout` as the test-cases are executed.
|
||||
|
||||
When `start.sh` script is used to run all test-cases, if any test fails, to help with debugging of the issue, the last 30 lines of `wpantund` logs of every node involved in the test-case is dumped to `stdout`.
|
||||
|
||||
A `wpan.Node()` instance can also provide additional logs and info as the test-cases are run (verbose mode). By default this is disabled. It can be enabled for a node instance when it is created:
|
||||
```python
|
||||
node = wpan.Node(verbose=True) # `node` instance will provide extra logs.
|
||||
```
|
||||
|
||||
Alternatively, `wpan.Node._VERBOSE` settings can be changed to enable verbose logging for all nodes.
|
||||
|
||||
Here is example of small test script and its corresponding log output with `verbose` mode enabled:
|
||||
|
||||
```python
|
||||
node1 = wpan.Node(verbose=True)
|
||||
node2 = wpan.Node(verbose=True)
|
||||
|
||||
wpan.Node.init_all_nodes()
|
||||
|
||||
node1.form("toranj-net")
|
||||
node2.active_scan()
|
||||
|
||||
node2.join_node(node1)
|
||||
verify(node2.get(wpan.WPAN_STATE) == wpan.STATE_ASSOCIATED)
|
||||
|
||||
lladdr1 = node1.get(wpan.WPAN_IP6_LINK_LOCAL_ADDRESS)[1:-1]
|
||||
lladdr2 = node2.get(wpan.WPAN_IP6_LINK_LOCAL_ADDRESS)[1:-1]
|
||||
|
||||
sender = node1.prepare_tx(lladdr1, lladdr2, 20)
|
||||
recver = node2.prepare_rx(sender)
|
||||
|
||||
wpan.Node.perform_async_tx_rx()
|
||||
|
||||
```
|
||||
```
|
||||
$ Node1.__init__() cmd: /usr/local/sbin/wpantund -o Config:NCP:SocketPath "system:../../examples/apps/ncp/ot-ncp-ftd 1" -o Config:TUN:InterfaceName wpan1 -o Config:NCP:DriverName spinel -o Daemon:SyslogMask "all -debug"
|
||||
$ Node2.__init__() cmd: /usr/local/sbin/wpantund -o Config:NCP:SocketPath "system:../../examples/apps/ncp/ot-ncp-ftd 2" -o Config:TUN:InterfaceName wpan2 -o Config:NCP:DriverName spinel -o Daemon:SyslogMask "all -debug"
|
||||
$ Node1.wpanctl('leave') -> 'Leaving current WPAN. . .'
|
||||
$ Node2.wpanctl('leave') -> 'Leaving current WPAN. . .'
|
||||
$ Node1.wpanctl('form "toranj-net"'):
|
||||
Forming WPAN "toranj-net" as node type "router"
|
||||
Successfully formed!
|
||||
$ Node2.wpanctl('scan'):
|
||||
| Joinable | NetworkName | PAN ID | Ch | XPanID | HWAddr | RSSI
|
||||
---+----------+--------------------+--------+----+------------------+------------------+------
|
||||
1 | NO | "toranj-net" | 0x9DEB | 16 | 8CC6CFC810F23E1B | BEECDAF3439DC931 | -20
|
||||
$ Node1.wpanctl('get -v NCP:State') -> '"associated"'
|
||||
$ Node1.wpanctl('get -v Network:Name') -> '"toranj-net"'
|
||||
$ Node1.wpanctl('get -v Network:PANID') -> '0x9DEB'
|
||||
$ Node1.wpanctl('get -v Network:XPANID') -> '0x8CC6CFC810F23E1B'
|
||||
$ Node1.wpanctl('get -v Network:Key') -> '[BA2733A5D81EAB8FFB3C9A7383CB6045]'
|
||||
$ Node1.wpanctl('get -v NCP:Channel') -> '16'
|
||||
$ Node2.wpanctl('set Network:Key -d -v BA2733A5D81EAB8FFB3C9A7383CB6045') -> ''
|
||||
$ Node2.wpanctl('join "toranj-net" -c 16 -T r -p 0x9DEB -x 0x8CC6CFC810F23E1B'):
|
||||
Joining "toranj-net" 8CC6CFC810F23E1B as node type "router"
|
||||
Successfully Joined!
|
||||
$ Node2.wpanctl('get -v NCP:State') -> '"associated"'
|
||||
$ Node1.wpanctl('get -v IPv6:LinkLocalAddress') -> '"fe80::bcec:daf3:439d:c931"'
|
||||
$ Node2.wpanctl('get -v IPv6:LinkLocalAddress') -> '"fe80::ec08:f348:646f:d37d"'
|
||||
- Node1 sent 20 bytes (":YeQuNKjuOtd%H#ipM7P") to [fe80::ec08:f348:646f:d37d]:404 from [fe80::bcec:daf3:439d:c931]:12557
|
||||
- Node2 received 20 bytes (":YeQuNKjuOtd%H#ipM7P") on port 404 from [fe80::bcec:daf3:439d:c931]:12557
|
||||
|
||||
```
|
||||
|
||||
------
|
||||
|
||||
What does `"toranj"` mean? it's the name of a common symmetric weaving [pattern](https://en.wikipedia.org/wiki/Persian_carpet#/media/File:Toranj_-_special_circular_design_of_Iranian_carpets.JPG) used in Persian carpets.
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 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.
|
||||
*/
|
||||
|
||||
#ifndef OPENTHREAD_CORE_TORANJ_CONFIG_H_
|
||||
#define OPENTHREAD_CORE_TORANJ_CONFIG_H_
|
||||
|
||||
/**
|
||||
* This header file defines the OpenThread core configuration options used in NCP build for `toranj` test framework.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_PLATFORM_INFO
|
||||
*
|
||||
* The platform-specific string to insert into the OpenThread version string.
|
||||
*
|
||||
*/
|
||||
#define OPENTHREAD_CONFIG_PLATFORM_INFO "POSIX-toranj"
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_NUM_MESSAGE_BUFFERS
|
||||
*
|
||||
* The number of message buffers in the buffer pool.
|
||||
*
|
||||
*/
|
||||
#define OPENTHREAD_CONFIG_NUM_MESSAGE_BUFFERS 256
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_ADDRESS_CACHE_ENTRIES
|
||||
*
|
||||
* The number of EID-to-RLOC cache entries.
|
||||
*
|
||||
*/
|
||||
#define OPENTHREAD_CONFIG_ADDRESS_CACHE_ENTRIES 32
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_ADDRESS_QUERY_MAX_RETRY_DELAY
|
||||
*
|
||||
* Maximum retry delay for address query (in seconds).
|
||||
*
|
||||
* Default: 28800 seconds (480 minutes or 8 hours)
|
||||
*
|
||||
*/
|
||||
#define OPENTHREAD_CONFIG_ADDRESS_QUERY_MAX_RETRY_DELAY 120
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_MAX_CHILDREN
|
||||
*
|
||||
* The maximum number of children.
|
||||
*
|
||||
*/
|
||||
#define OPENTHREAD_CONFIG_MAX_CHILDREN 32
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_DEFAULT_CHILD_TIMEOUT
|
||||
*
|
||||
* The default child timeout value (in seconds).
|
||||
*
|
||||
*/
|
||||
#define OPENTHREAD_CONFIG_DEFAULT_CHILD_TIMEOUT 120
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_IP_ADDRS_PER_CHILD
|
||||
*
|
||||
* The maximum number of supported IPv6 address registrations per child.
|
||||
*
|
||||
*/
|
||||
#define OPENTHREAD_CONFIG_IP_ADDRS_PER_CHILD 10
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_MAX_EXT_IP_ADDRS
|
||||
*
|
||||
* The maximum number of supported IPv6 addresses allows to be externally added.
|
||||
*
|
||||
*/
|
||||
#define OPENTHREAD_CONFIG_MAX_EXT_IP_ADDRS 8
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_MAX_EXT_MULTICAST_IP_ADDRS
|
||||
*
|
||||
* The maximum number of supported IPv6 multicast addresses allows to be externally added.
|
||||
*
|
||||
*/
|
||||
#define OPENTHREAD_CONFIG_MAX_EXT_MULTICAST_IP_ADDRS 4
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_MAC_FILTER_SIZE
|
||||
*
|
||||
* The number of MAC Filter entries.
|
||||
*
|
||||
*/
|
||||
#define OPENTHREAD_CONFIG_MAC_FILTER_SIZE 80
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_LOG_OUTPUT
|
||||
*
|
||||
* Selects if, and where the LOG output goes to.
|
||||
*
|
||||
*/
|
||||
#define OPENTHREAD_CONFIG_LOG_OUTPUT OPENTHREAD_CONFIG_LOG_OUTPUT_APP
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_LOG_LEVEL
|
||||
*
|
||||
* The log level (used at compile time).
|
||||
*
|
||||
*/
|
||||
#define OPENTHREAD_CONFIG_LOG_LEVEL OT_LOG_LEVEL_INFO
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_ENABLE_DYNAMIC_LOG_LEVEL
|
||||
*
|
||||
* Define as 1 to enable dynamic log level control.
|
||||
*
|
||||
*/
|
||||
#define OPENTHREAD_CONFIG_ENABLE_DYNAMIC_LOG_LEVEL 1
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_LOG_SUFFIX
|
||||
*
|
||||
* Define suffix to append at the end of logs.
|
||||
*
|
||||
*/
|
||||
#define OPENTHREAD_CONFIG_LOG_SUFFIX "\n"
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_NCP_TX_BUFFER_SIZE
|
||||
*
|
||||
* The size of NCP message buffer in bytes
|
||||
*
|
||||
*/
|
||||
#define OPENTHREAD_CONFIG_NCP_TX_BUFFER_SIZE 4096
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_ENABLE_STEERING_DATA_SET_OOB
|
||||
*
|
||||
* Enable setting steering data out of band.
|
||||
*
|
||||
*/
|
||||
#define OPENTHREAD_CONFIG_ENABLE_STEERING_DATA_SET_OOB 1
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_INFORM_PREVIOUS_PARENT_ON_REATTACH
|
||||
*
|
||||
* Define as 1 for a child to inform its previous parent when it attaches to a new parent.
|
||||
*
|
||||
* If this feature is enabled, when a device attaches to a new parent, it will send an IP message (with empty payload
|
||||
* and mesh-local IP address as the source address) to its previous parent.
|
||||
*
|
||||
*/
|
||||
#define OPENTHREAD_CONFIG_INFORM_PREVIOUS_PARENT_ON_REATTACH 1
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_MLE_SEND_LINK_REQUEST_ON_ADV_TIMEOUT
|
||||
*
|
||||
* Define to 1 to send an MLE Link Request when MAX_NEIGHBOR_AGE is reached for a neighboring router.
|
||||
*
|
||||
*/
|
||||
#define OPENTHREAD_CONFIG_MLE_SEND_LINK_REQUEST_ON_ADV_TIMEOUT 1
|
||||
|
||||
|
||||
#endif /* OPENTHREAD_CORE_TORANJ_CONFIG_H_ */
|
||||
|
||||
Executable
+81
@@ -0,0 +1,81 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Copyright (c) 2018, 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.
|
||||
#
|
||||
|
||||
die() {
|
||||
echo " *** ERROR: " $*
|
||||
exit 1
|
||||
}
|
||||
|
||||
failed() {
|
||||
echo " *** TEST FAILED: ERROR: " $*
|
||||
tail -n 30 wpantund-logs*.log
|
||||
exit 1
|
||||
}
|
||||
|
||||
clean() {
|
||||
sudo rm tmp/*.flash > /dev/null 2>&1
|
||||
sudo rm *.log > /dev/null 2>&1
|
||||
}
|
||||
|
||||
cd $(dirname $0)
|
||||
cd ../..
|
||||
|
||||
# Build OpenThread posix mode with required configuration
|
||||
|
||||
./bootstrap || die
|
||||
./configure \
|
||||
CPPFLAGS='-DOPENTHREAD_PROJECT_CORE_CONFIG_FILE=\"../tests/toranj/openthread-core-toranj-config.h\"' \
|
||||
--enable-ncp-app=all \
|
||||
--with-ncp-bus=uart \
|
||||
--with-examples=posix \
|
||||
--enable-border-router \
|
||||
--enable-child-supervision \
|
||||
--enable-diag \
|
||||
--enable-jam-detection \
|
||||
--enable-legacy \
|
||||
--enable-mac-filter \
|
||||
--enable-service \
|
||||
--enable-channel-monitor \
|
||||
--disable-docs \
|
||||
--disable-test || die
|
||||
|
||||
make -j 8 || die
|
||||
|
||||
# Run all the tests
|
||||
|
||||
cd tests/toranj
|
||||
|
||||
clean; sudo python test-001-get-set.py || failed
|
||||
clean; sudo python test-002-form.py || failed
|
||||
clean; sudo python test-003-join.py || failed
|
||||
clean; sudo python test-004-scan.py || failed
|
||||
clean; sudo python test-005-discover-scan.py || failed
|
||||
clean; sudo python test-006-traffic-router-end-device.py || failed
|
||||
clean; sudo python test-007-traffic-router-sleepy.py || failed
|
||||
clean; sudo python test-008-permit-join.py || failed
|
||||
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (c) 2018, 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 wpan import verify
|
||||
import wpan
|
||||
import time
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Test description: simple wpanctl get and set commands
|
||||
|
||||
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
|
||||
print '-' * 120
|
||||
print 'Starting \'{}\''.format(test_name)
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Creating `wpan.Nodes` instances
|
||||
|
||||
node = wpan.Node()
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Init all nodes
|
||||
|
||||
wpan.Node.init_all_nodes()
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Test implementation
|
||||
|
||||
verify(node.get(wpan.WPAN_STATE) == wpan.STATE_OFFLINE)
|
||||
|
||||
# set some of properties and check and verify that the value is indeed changed...
|
||||
|
||||
node.set(wpan.WPAN_NAME, 'test-network')
|
||||
verify(node.get(wpan.WPAN_NAME) == '"test-network"')
|
||||
|
||||
node.set(wpan.WPAN_NAME, 'a')
|
||||
verify(node.get(wpan.WPAN_NAME) == '"a"')
|
||||
|
||||
node.set(wpan.WPAN_PANID, '0xABBA')
|
||||
verify(node.get(wpan.WPAN_PANID) == '0xABBA')
|
||||
|
||||
node.set(wpan.WPAN_XPANID, '1020031510006016', binary_data=True)
|
||||
verify(node.get(wpan.WPAN_XPANID) == '0x1020031510006016')
|
||||
|
||||
node.set(wpan.WPAN_KEY, '0123456789abcdeffecdba9876543210', binary_data=True)
|
||||
verify(node.get(wpan.WPAN_KEY) == '[0123456789ABCDEFFECDBA9876543210]')
|
||||
|
||||
node.set(wpan.WPAN_MAC_WHITELIST_ENABLED, '1')
|
||||
verify(node.get(wpan.WPAN_MAC_WHITELIST_ENABLED) == 'true')
|
||||
|
||||
node.set(wpan.WPAN_MAC_WHITELIST_ENABLED, '0')
|
||||
verify(node.get(wpan.WPAN_MAC_WHITELIST_ENABLED) == 'false')
|
||||
|
||||
node.set(wpan.WPAN_MAC_WHITELIST_ENABLED, 'true')
|
||||
verify(node.get(wpan.WPAN_MAC_WHITELIST_ENABLED) == 'true')
|
||||
|
||||
# Ensure `get` is successful with all gettable properties
|
||||
|
||||
all_gettable_props = [
|
||||
wpan.WPAN_STATE,
|
||||
wpan.WPAN_NAME,
|
||||
wpan.WPAN_PANID,
|
||||
wpan.WPAN_XPANID,
|
||||
wpan.WPAN_KEY,
|
||||
wpan.WPAN_CHANNEL,
|
||||
wpan.WPAN_HW_ADDRESS,
|
||||
wpan.WPAN_EXT_ADDRESS,
|
||||
wpan.WPAN_POLL_INTERVAL,
|
||||
wpan.WPAN_NODE_TYPE,
|
||||
wpan.WPAN_ROLE,
|
||||
wpan.WPAN_PARTITION_ID,
|
||||
wpan.WPAN_NCP_VERSION,
|
||||
wpan.WPAN_IP6_LINK_LOCAL_ADDRESS,
|
||||
wpan.WPAN_IP6_MESH_LOCAL_ADDRESS,
|
||||
wpan.WPAN_IP6_MESH_LOCAL_PREFIX,
|
||||
wpan.WPAN_IP6_ALL_ADDRESSES,
|
||||
wpan.WPAN_IP6_MULTICAST_ADDRESSES,
|
||||
wpan.WPAN_THREAD_RLOC16,
|
||||
wpan.WPAN_THREAD_ROUTER_ID,
|
||||
wpan.WPAN_THREAD_NETWORK_DATA,
|
||||
wpan.WPAN_THREAD_CHILD_TABLE,
|
||||
wpan.WPAN_THREAD_CHILD_TABLE_ASVALMAP,
|
||||
wpan.WPAN_THREAD_CHILD_TABLE_ADDRESSES,
|
||||
wpan.WPAN_THREAD_NEIGHBOR_TABLE,
|
||||
wpan.WPAN_THREAD_NEIGHBOR_TABLE_ASVALMAP,
|
||||
wpan.WPAN_THREAD_ROUTER_TABLE,
|
||||
wpan.WPAN_THREAD_ROUTER_TABLE_ASVALMAP,
|
||||
wpan.WPAN_THREAD_NETWORK_DATA_VERSION,
|
||||
wpan.WPAN_THREAD_STABLE_NETWORK_DATA,
|
||||
wpan.WPAN_THREAD_STABLE_NETWORK_DATA_VERSION,
|
||||
wpan.WPAN_THREAD_DEVICE_MODE,
|
||||
wpan.WPAN_THREAD_OFF_MESH_ROUTES,
|
||||
wpan.WPAN_THREAD_ON_MESH_PREFIXES,
|
||||
wpan.WPAN_THREAD_ROUTER_ROLE_ENABLED,
|
||||
wpan.WPAN_THREAD_CONFIG_FILTER_RLOC_ADDRESSES,
|
||||
wpan.WPAN_THREAD_ACTIVE_DATASET,
|
||||
wpan.WPAN_THREAD_ACTIVE_DATASET_ASVALMAP,
|
||||
wpan.WPAN_THREAD_PENDING_DATASET,
|
||||
wpan.WPAN_THREAD_PENDING_DATASET_ASVALMAP,
|
||||
wpan.WPAN_OT_LOG_LEVEL,
|
||||
wpan.WPAN_OT_STEERING_DATA_ADDRESS,
|
||||
wpan.WPAN_OT_STEERING_DATA_SET_WHEN_JOINABLE,
|
||||
wpan.WPAN_OT_MSG_BUFFER_COUNTERS,
|
||||
wpan.WPAN_OT_MSG_BUFFER_COUNTERS_AS_STRING,
|
||||
wpan.WPAN_NCP_COUNTER_ALL_MAC,
|
||||
wpan.WPAN_NCP_COUNTER_ALL_MAC_ASVALMAP,
|
||||
wpan.WPAN_MAC_WHITELIST_ENABLED,
|
||||
wpan.WPAN_MAC_WHITELIST_ENTRIES,
|
||||
wpan.WPAN_MAC_WHITELIST_ENTRIES_ASVALMAP,
|
||||
wpan.WPAN_MAC_BLACKLIST_ENABLED,
|
||||
wpan.WPAN_MAC_BLACKLIST_ENTRIES,
|
||||
wpan.WPAN_MAC_BLACKLIST_ENTRIES_ASVALMAP,
|
||||
wpan.WPAN_JAM_DETECTION_STATUS,
|
||||
wpan.WPAN_JAM_DETECTION_ENABLE,
|
||||
wpan.WPAN_JAM_DETECTION_RSSI_THRESHOLD,
|
||||
wpan.WPAN_JAM_DETECTION_WINDOW,
|
||||
wpan.WPAN_JAM_DETECTION_BUSY_PERIOD,
|
||||
wpan.WPAN_JAM_DETECTION_DEBUG_HISTORY_BITMAP,
|
||||
wpan.WPAN_CHANNEL_MONITOR_SAMPLE_INTERVAL,
|
||||
wpan.WPAN_CHANNEL_MONITOR_RSSI_THRESHOLD,
|
||||
wpan.WPAN_CHANNEL_MONITOR_SAMPLE_WINDOW,
|
||||
wpan.WPAN_CHANNEL_MONITOR_SAMPLE_COUNT,
|
||||
wpan.WPAN_CHANNEL_MONITOR_CHANNEL_QUALITY,
|
||||
wpan.WPAN_CHANNEL_MONITOR_CHANNEL_QUALITY_ASVALMAP,
|
||||
]
|
||||
|
||||
for prop in all_gettable_props:
|
||||
node.get(prop)
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Test finished
|
||||
|
||||
print '\'{}\' passed.'.format(test_name)
|
||||
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (c) 2018, 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 wpan import verify
|
||||
import wpan
|
||||
import time
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Test description: forming a Thread network
|
||||
|
||||
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
|
||||
print '-' * 120
|
||||
print 'Starting \'{}\''.format(test_name)
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Creating `wpan.Nodes` instances
|
||||
|
||||
node = wpan.Node()
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Init all nodes
|
||||
|
||||
wpan.Node.init_all_nodes()
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Test implementation
|
||||
|
||||
# default values after reset
|
||||
DEFAULT_KEY = '[00112233445566778899AABBCCDDEEFF]'
|
||||
DEFAULT_NAME = '"OpenThread"'
|
||||
DEFAULT_PANID = '0xFFFF'
|
||||
DEFAULT_XPANID = '0xDEAD00BEEF00CAFE'
|
||||
|
||||
verify(node.get(wpan.WPAN_STATE) == wpan.STATE_OFFLINE)
|
||||
verify(node.get(wpan.WPAN_KEY) == DEFAULT_KEY)
|
||||
verify(node.get(wpan.WPAN_NAME) == DEFAULT_NAME)
|
||||
verify(node.get(wpan.WPAN_PANID) == DEFAULT_PANID)
|
||||
verify(node.get(wpan.WPAN_XPANID) == DEFAULT_XPANID)
|
||||
|
||||
# Form a network
|
||||
|
||||
node.form('asha')
|
||||
verify(node.get(wpan.WPAN_STATE) == wpan.STATE_ASSOCIATED)
|
||||
verify(node.get(wpan.WPAN_NODE_TYPE) == wpan.NODE_TYPE_LEADER)
|
||||
verify(node.get(wpan.WPAN_NAME) == '"asha"')
|
||||
verify(node.get(wpan.WPAN_KEY) != DEFAULT_KEY)
|
||||
verify(node.get(wpan.WPAN_PANID) != DEFAULT_PANID)
|
||||
verify(node.get(wpan.WPAN_XPANID) != DEFAULT_XPANID)
|
||||
|
||||
|
||||
node.leave()
|
||||
verify(node.get(wpan.WPAN_STATE) == wpan.STATE_OFFLINE)
|
||||
|
||||
# Form a network on a specific channel.
|
||||
|
||||
node.form('ahura', channel=20)
|
||||
verify(node.get(wpan.WPAN_STATE) == wpan.STATE_ASSOCIATED)
|
||||
verify(node.get(wpan.WPAN_NAME) == '"ahura"')
|
||||
verify(node.get(wpan.WPAN_CHANNEL) == '20')
|
||||
verify(node.get(wpan.WPAN_KEY) != DEFAULT_KEY)
|
||||
verify(node.get(wpan.WPAN_PANID) != DEFAULT_PANID)
|
||||
verify(node.get(wpan.WPAN_XPANID) != DEFAULT_XPANID)
|
||||
|
||||
node.leave()
|
||||
verify(node.get(wpan.WPAN_STATE) == wpan.STATE_OFFLINE)
|
||||
|
||||
# Form a network with a specific panid, xpanid and key.
|
||||
|
||||
node.set(wpan.WPAN_PANID, '0x1977')
|
||||
node.set(wpan.WPAN_XPANID, '1020031510006016', binary_data=True)
|
||||
node.set(wpan.WPAN_KEY, '0123456789abcdeffecdba9876543210', binary_data=True)
|
||||
|
||||
node.form('mazda', channel=12)
|
||||
verify(node.get(wpan.WPAN_STATE) == wpan.STATE_ASSOCIATED)
|
||||
verify(node.get(wpan.WPAN_NAME) == '"mazda"')
|
||||
verify(node.get(wpan.WPAN_CHANNEL) == '12')
|
||||
verify(node.get(wpan.WPAN_KEY) == '[0123456789ABCDEFFECDBA9876543210]')
|
||||
verify(node.get(wpan.WPAN_PANID) == '0x1977')
|
||||
verify(node.get(wpan.WPAN_XPANID) == '0x1020031510006016')
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Test finished
|
||||
|
||||
print '\'{}\' passed.'.format(test_name)
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (c) 2018, 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 wpan import verify
|
||||
import wpan
|
||||
import time
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Test description: joining (as router, end-device, sleepy) - two node network
|
||||
|
||||
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
|
||||
print '-' * 120
|
||||
print 'Starting \'{}\''.format(test_name)
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Creating `wpan.Nodes` instances
|
||||
|
||||
node1 = wpan.Node()
|
||||
node2 = wpan.Node()
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Init all nodes
|
||||
|
||||
wpan.Node.init_all_nodes()
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Test implementation
|
||||
|
||||
# Form a network on node1
|
||||
node1.form('PAN-aB71n')
|
||||
verify(node1.get(wpan.WPAN_STATE) == wpan.STATE_ASSOCIATED)
|
||||
verify(node1.get(wpan.WPAN_NODE_TYPE) == wpan.NODE_TYPE_LEADER)
|
||||
|
||||
# Join from node2 as a router
|
||||
node2.join_node(node1, node_type=wpan.JOIN_TYPE_ROUTER)
|
||||
verify(node2.get(wpan.WPAN_STATE) == wpan.STATE_ASSOCIATED)
|
||||
verify(node2.get(wpan.WPAN_NAME) == node1.get(wpan.WPAN_NAME))
|
||||
verify(node2.get(wpan.WPAN_PANID) == node1.get(wpan.WPAN_PANID))
|
||||
verify(node2.get(wpan.WPAN_XPANID) == node1.get(wpan.WPAN_XPANID))
|
||||
verify(node2.get(wpan.WPAN_KEY) == node1.get(wpan.WPAN_KEY))
|
||||
|
||||
node2.leave()
|
||||
|
||||
# Join from node2 as an end-device
|
||||
node2.join_node(node1, node_type=wpan.JOIN_TYPE_END_DEVICE)
|
||||
verify(node2.get(wpan.WPAN_STATE) == wpan.STATE_ASSOCIATED)
|
||||
verify(node2.get(wpan.WPAN_NAME) == node1.get(wpan.WPAN_NAME))
|
||||
verify(node2.get(wpan.WPAN_PANID) == node1.get(wpan.WPAN_PANID))
|
||||
verify(node2.get(wpan.WPAN_XPANID) == node1.get(wpan.WPAN_XPANID))
|
||||
verify(node2.get(wpan.WPAN_KEY) == node1.get(wpan.WPAN_KEY))
|
||||
|
||||
node2.leave()
|
||||
|
||||
# Join from node2 as a sleepy-end-device
|
||||
node2.join_node(node1, node_type=wpan.JOIN_TYPE_SLEEPY_END_DEVICE)
|
||||
verify(node2.get(wpan.WPAN_STATE) == wpan.STATE_ASSOCIATED)
|
||||
verify(node2.get(wpan.WPAN_NAME) == node1.get(wpan.WPAN_NAME))
|
||||
verify(node2.get(wpan.WPAN_PANID) == node1.get(wpan.WPAN_PANID))
|
||||
verify(node2.get(wpan.WPAN_XPANID) == node1.get(wpan.WPAN_XPANID))
|
||||
verify(node2.get(wpan.WPAN_KEY) == node1.get(wpan.WPAN_KEY))
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Test finished
|
||||
|
||||
print '\'{}\' passed.'.format(test_name)
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (c) 2018, 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 wpan import verify
|
||||
import wpan
|
||||
import time
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Test desciption: Active scan and permit-join
|
||||
|
||||
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
|
||||
print '-' * 120
|
||||
print 'Starting \'{}\''.format(test_name)
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Creating `wpan.Nodes` instances
|
||||
|
||||
NUM_NODES = 5
|
||||
|
||||
nodes = []
|
||||
for i in range(NUM_NODES):
|
||||
nodes.append(wpan.Node())
|
||||
|
||||
scanner = wpan.Node()
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Init all nodes
|
||||
|
||||
wpan.Node.init_all_nodes()
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Build network topology
|
||||
|
||||
for node in nodes:
|
||||
node.form(node.interface_name)
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Test implementation
|
||||
|
||||
# Perform active scan and check that all nodes are seen in the scan result.
|
||||
|
||||
scan_result = wpan.parse_scan_result(scanner.active_scan())
|
||||
|
||||
for node in nodes:
|
||||
verify(node.is_in_scan_result(scan_result))
|
||||
|
||||
# Make every other network joinable, scan and check the result.
|
||||
|
||||
make_joinable = False
|
||||
for node in nodes:
|
||||
make_joinable = not make_joinable
|
||||
if make_joinable:
|
||||
node.permit_join()
|
||||
|
||||
scan_result = wpan.parse_scan_result(scanner.active_scan())
|
||||
|
||||
for node in nodes:
|
||||
verify(node.is_in_scan_result(scan_result))
|
||||
|
||||
# Scan from an already associated node.
|
||||
|
||||
scan_result = wpan.parse_scan_result(nodes[0].active_scan())
|
||||
|
||||
for node in nodes[1:]:
|
||||
verify(node.is_in_scan_result(scan_result))
|
||||
|
||||
# Scan on a specific channel
|
||||
|
||||
channel = nodes[0].get(wpan.WPAN_CHANNEL)
|
||||
scan_result = wpan.parse_scan_result(scanner.active_scan(channel=channel))
|
||||
verify(nodes[0].is_in_scan_result(scan_result))
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Test finished
|
||||
|
||||
print '\'{}\' passed.'.format(test_name)
|
||||
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (c) 2018, 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 wpan import verify
|
||||
import wpan
|
||||
import time
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Test description: discover scan
|
||||
|
||||
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
|
||||
print '-' * 120
|
||||
print 'Starting \'{}\''.format(test_name)
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Creating `wpan.Nodes` instances
|
||||
|
||||
NUM_NODES = 5
|
||||
|
||||
nodes = []
|
||||
for i in range(NUM_NODES):
|
||||
nodes.append(wpan.Node())
|
||||
|
||||
scanner = wpan.Node()
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Init all nodes
|
||||
|
||||
wpan.Node.init_all_nodes()
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Build network topology
|
||||
|
||||
for node in nodes:
|
||||
node.form(node.interface_name)
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Test implementation
|
||||
|
||||
# Perform active scan and check that all nodes are seen in the scan result.
|
||||
|
||||
scan_result = wpan.parse_scan_result(scanner.discover_scan())
|
||||
|
||||
for node in nodes:
|
||||
verify(node.is_in_scan_result(scan_result))
|
||||
|
||||
# Scan from an already associated node.
|
||||
|
||||
scan_result = wpan.parse_scan_result(nodes[0].discover_scan())
|
||||
|
||||
for node in nodes[1:]:
|
||||
verify(node.is_in_scan_result(scan_result))
|
||||
|
||||
# TODO: add tests for the joiner only and filtering
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Test finished
|
||||
|
||||
print '\'{}\' passed.'.format(test_name)
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (c) 2018, 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.
|
||||
|
||||
import time
|
||||
import wpan
|
||||
from wpan import verify
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Test description: Traffic between router and end-device (link-local and mesh-local IPv6 addresses)
|
||||
|
||||
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
|
||||
print '-' * 120
|
||||
print 'Starting \'{}\''.format(test_name)
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Creating `wpan.Nodes` instances
|
||||
|
||||
node1 = wpan.Node()
|
||||
node2 = wpan.Node()
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Init all nodes
|
||||
|
||||
wpan.Node.init_all_nodes()
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Build network topology
|
||||
|
||||
# Two-node network (node1 leader/router, node2 end-device)
|
||||
|
||||
node1.form('test-PAN')
|
||||
node2.join_node(node1, node_type=wpan.JOIN_TYPE_END_DEVICE)
|
||||
|
||||
verify(node2.get(wpan.WPAN_STATE) == wpan.STATE_ASSOCIATED)
|
||||
verify(node2.get(wpan.WPAN_NAME) == node1.get(wpan.WPAN_NAME))
|
||||
verify(node2.get(wpan.WPAN_PANID) == node1.get(wpan.WPAN_PANID))
|
||||
verify(node2.get(wpan.WPAN_XPANID) == node1.get(wpan.WPAN_XPANID))
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Test implementation
|
||||
|
||||
# Get the link local addresses
|
||||
ll1 = node1.get(wpan.WPAN_IP6_LINK_LOCAL_ADDRESS)[1:-1]
|
||||
ll2 = node2.get(wpan.WPAN_IP6_LINK_LOCAL_ADDRESS)[1:-1]
|
||||
|
||||
# Get the mesh-local addresses
|
||||
ml1 = node1.get(wpan.WPAN_IP6_MESH_LOCAL_ADDRESS)[1:-1]
|
||||
ml2 = node2.get(wpan.WPAN_IP6_MESH_LOCAL_ADDRESS)[1:-1]
|
||||
|
||||
NUM_MSGS = 3
|
||||
MSG_LENS = [40, 100, 400, 800, 1000]
|
||||
PORT = 1234
|
||||
|
||||
# all src and dst configuration (link-local and mesh-local)
|
||||
for src,dst in [ (ll1, ll2), (ll1, ml2), (ml1, ll2), (ml1, ml2) ]:
|
||||
|
||||
for msg_length in MSG_LENS:
|
||||
sender = node1.prepare_tx(src, dst, msg_length, NUM_MSGS)
|
||||
recver = node2.prepare_rx(sender)
|
||||
|
||||
wpan.Node.perform_async_tx_rx()
|
||||
|
||||
verify(sender.was_successful)
|
||||
verify(recver.was_successful)
|
||||
|
||||
# Send and receive at the same time on same port number
|
||||
|
||||
s1 = node1.prepare_tx((src, PORT), (dst, PORT), 'Hi there!', NUM_MSGS)
|
||||
r1 = node2.prepare_rx(s1)
|
||||
s2 = node2.prepare_tx((dst, PORT), (src, PORT), 'Hello back to you!', NUM_MSGS)
|
||||
r2 = node1.prepare_rx(s2)
|
||||
|
||||
wpan.Node.perform_async_tx_rx()
|
||||
|
||||
verify(s1.was_successful and r1.was_successful)
|
||||
verify(s2.was_successful and r2.was_successful)
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Test finished
|
||||
|
||||
print '\'{}\' passed.'.format(test_name)
|
||||
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (c) 2018, 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.
|
||||
|
||||
import time
|
||||
import wpan
|
||||
from wpan import verify
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Test description: Traffic between router and sleepy-end-device (link-local and mesh-local IPv6 addresses)
|
||||
|
||||
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
|
||||
print '-' * 120
|
||||
print 'Starting \'{}\''.format(test_name)
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Creating `wpan.Nodes` instances
|
||||
|
||||
node1 = wpan.Node()
|
||||
node2 = wpan.Node()
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Init all nodes
|
||||
|
||||
wpan.Node.init_all_nodes()
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Build network topology
|
||||
|
||||
# Two-node network (node1 leader/router, node2 sleepy-end-device)
|
||||
|
||||
node1.form('test-PAN')
|
||||
node2.join_node(node1, node_type=wpan.JOIN_TYPE_SLEEPY_END_DEVICE)
|
||||
|
||||
verify(node2.get(wpan.WPAN_STATE) == wpan.STATE_ASSOCIATED)
|
||||
verify(node2.get(wpan.WPAN_NAME) == node1.get(wpan.WPAN_NAME))
|
||||
verify(node2.get(wpan.WPAN_PANID) == node1.get(wpan.WPAN_PANID))
|
||||
verify(node2.get(wpan.WPAN_XPANID) == node1.get(wpan.WPAN_XPANID))
|
||||
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Test implementation
|
||||
|
||||
# Get the link local addresses
|
||||
ll1 = node1.get(wpan.WPAN_IP6_LINK_LOCAL_ADDRESS)[1:-1]
|
||||
ll2 = node2.get(wpan.WPAN_IP6_LINK_LOCAL_ADDRESS)[1:-1]
|
||||
|
||||
# Get the mesh-local addresses
|
||||
ml1 = node1.get(wpan.WPAN_IP6_MESH_LOCAL_ADDRESS)[1:-1]
|
||||
ml2 = node2.get(wpan.WPAN_IP6_MESH_LOCAL_ADDRESS)[1:-1]
|
||||
|
||||
NUM_MSGS = 3
|
||||
MSG_LENS = [40, 100, 400, 800, 1000]
|
||||
PORT = 1234
|
||||
|
||||
for poll_interval in [10, 100, 300]:
|
||||
|
||||
node2.set(wpan.WPAN_POLL_INTERVAL, str(poll_interval))
|
||||
verify(node2.get(wpan.WPAN_POLL_INTERVAL) == str(poll_interval))
|
||||
|
||||
# all src and dst configuration (link-local and mesh-local)
|
||||
for src,dst in [ (ll1, ll2), (ll1, ml2), (ml1, ll2), (ml1, ml2) ]:
|
||||
|
||||
for msg_length in MSG_LENS:
|
||||
sender = node1.prepare_tx(src, dst, msg_length, NUM_MSGS)
|
||||
recver = node2.prepare_rx(sender)
|
||||
|
||||
wpan.Node.perform_async_tx_rx()
|
||||
|
||||
verify(sender.was_successful)
|
||||
verify(recver.was_successful)
|
||||
|
||||
# Send and receive at the same time on same port number
|
||||
|
||||
s1 = node1.prepare_tx((src, PORT), (dst, PORT), 'Hi there!', NUM_MSGS)
|
||||
r1 = node2.prepare_rx(s1)
|
||||
s2 = node2.prepare_tx((dst, PORT), (src, PORT), 'Hello back to you!', NUM_MSGS)
|
||||
r2 = node1.prepare_rx(s2)
|
||||
|
||||
wpan.Node.perform_async_tx_rx()
|
||||
|
||||
verify(s1.was_successful and r1.was_successful)
|
||||
verify(s2.was_successful and r2.was_successful)
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Test finished
|
||||
|
||||
print '\'{}\' passed.'.format(test_name)
|
||||
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (c) 2018, 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.
|
||||
|
||||
import time
|
||||
import wpan
|
||||
from wpan import verify
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Test description: check wpantund `permit-join` functionality and timeout
|
||||
|
||||
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
|
||||
print '-' * 120
|
||||
print 'Starting \'{}\''.format(test_name)
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Creating `wpan.Nodes` instances
|
||||
|
||||
node = wpan.Node()
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Init all nodes
|
||||
|
||||
wpan.Node.init_all_nodes()
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Build network topology
|
||||
|
||||
node.form("permit-join-test")
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Test implementation
|
||||
|
||||
verify(node.get(wpan.WPAN_NETWORK_ALLOW_JOIN) == 'false')
|
||||
|
||||
node.permit_join()
|
||||
verify(node.get(wpan.WPAN_NETWORK_ALLOW_JOIN) == 'true')
|
||||
|
||||
node.permit_join('0')
|
||||
verify(node.get(wpan.WPAN_NETWORK_ALLOW_JOIN) == 'false')
|
||||
|
||||
node.permit_join(port='1234')
|
||||
verify(node.get(wpan.WPAN_NETWORK_ALLOW_JOIN) == 'true')
|
||||
|
||||
node.permit_join('0')
|
||||
verify(node.get(wpan.WPAN_NETWORK_ALLOW_JOIN) == 'false')
|
||||
|
||||
# check the timeout
|
||||
node.permit_join('1')
|
||||
verify(node.get(wpan.WPAN_NETWORK_ALLOW_JOIN) == 'true')
|
||||
time.sleep(1.5)
|
||||
verify(node.get(wpan.WPAN_NETWORK_ALLOW_JOIN) == 'false')
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Test finished
|
||||
|
||||
print '\'{}\' passed.'.format(test_name)
|
||||
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (c) 2018, 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.
|
||||
|
||||
import time
|
||||
import wpan
|
||||
from wpan import verify
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Test description: TODO
|
||||
|
||||
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
|
||||
print '-' * 120
|
||||
print 'Starting \'{}\''.format(test_name)
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Creating `wpan.Nodes` instances
|
||||
|
||||
#TODO
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Init all nodes
|
||||
|
||||
wpan.Node.init_all_nodes()
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Build network topology
|
||||
|
||||
#TODO
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Test implementation
|
||||
|
||||
#TODO
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Test finished
|
||||
|
||||
print '\'{}\' passed.'.format(test_name)
|
||||
@@ -0,0 +1,801 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (c) 2018, 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.
|
||||
#
|
||||
|
||||
import sys
|
||||
import time
|
||||
import random
|
||||
import weakref
|
||||
import subprocess
|
||||
import socket
|
||||
import asyncore
|
||||
|
||||
#----------------------------------------------------------------------------------------------------------------------
|
||||
# wpantund properties
|
||||
|
||||
WPAN_STATE = 'NCP:State'
|
||||
WPAN_NAME = 'Network:Name'
|
||||
WPAN_PANID = 'Network:PANID'
|
||||
WPAN_XPANID = 'Network:XPANID'
|
||||
WPAN_KEY = 'Network:Key'
|
||||
WPAN_CHANNEL = 'NCP:Channel'
|
||||
WPAN_HW_ADDRESS = 'NCP:HardwareAddress'
|
||||
WPAN_EXT_ADDRESS = 'NCP:ExtendedAddress'
|
||||
WPAN_POLL_INTERVAL = 'NCP:SleepyPollInterval'
|
||||
WPAN_NODE_TYPE = 'Network:NodeType'
|
||||
WPAN_ROLE = 'Network:Role'
|
||||
WPAN_PARTITION_ID = 'Network:PartitionId'
|
||||
WPAN_NCP_VERSION = 'NCP:Version'
|
||||
WPAN_NETWORK_ALLOW_JOIN = 'com.nestlabs.internal:Network:AllowingJoin'
|
||||
WPAN_NETWORK_PASSTHRU_PORT = 'com.nestlabs.internal:Network:PassthruPort'
|
||||
|
||||
WPAN_IP6_LINK_LOCAL_ADDRESS = "IPv6:LinkLocalAddress"
|
||||
WPAN_IP6_MESH_LOCAL_ADDRESS = "IPv6:MeshLocalAddress"
|
||||
WPAN_IP6_MESH_LOCAL_PREFIX = "IPv6:MeshLocalPrefix"
|
||||
WPAN_IP6_ALL_ADDRESSES = "IPv6:AllAddresses"
|
||||
WPAN_IP6_MULTICAST_ADDRESSES = "IPv6:MulticastAddresses"
|
||||
|
||||
WPAN_THREAD_RLOC16 = "Thread:RLOC16"
|
||||
WPAN_THREAD_ROUTER_ID = "Thread:RouterID"
|
||||
WPAN_THREAD_LEADER_ADDRESS = "Thread:Leader:Address"
|
||||
WPAN_THREAD_LEADER_ROUTER_ID = "Thread:Leader:RouterID"
|
||||
WPAN_THREAD_LEADER_WEIGHT = "Thread:Leader:Weight"
|
||||
WPAN_THREAD_LEADER_LOCAL_WEIGHT = "Thread:Leader:LocalWeight"
|
||||
WPAN_THREAD_LEADER_NETWORK_DATA = "Thread:Leader:NetworkData"
|
||||
WPAN_THREAD_STABLE_LEADER_NETWORK_DATA = "Thread:Leader:StableNetworkData"
|
||||
WPAN_THREAD_NETWORK_DATA = "Thread:NetworkData"
|
||||
WPAN_THREAD_CHILD_TABLE = "Thread:ChildTable"
|
||||
WPAN_THREAD_CHILD_TABLE_ASVALMAP = "Thread:ChildTable:AsValMap"
|
||||
WPAN_THREAD_CHILD_TABLE_ADDRESSES = "Thread:ChildTable:Addresses"
|
||||
WPAN_THREAD_NEIGHBOR_TABLE = "Thread:NeighborTable"
|
||||
WPAN_THREAD_NEIGHBOR_TABLE_ASVALMAP = "Thread:NeighborTable:AsValMap"
|
||||
WPAN_THREAD_ROUTER_TABLE = "Thread:RouterTable"
|
||||
WPAN_THREAD_ROUTER_TABLE_ASVALMAP = "Thread:RouterTable:AsValMap"
|
||||
WPAN_THREAD_NETWORK_DATA_VERSION = "Thread:NetworkDataVersion"
|
||||
WPAN_THREAD_STABLE_NETWORK_DATA = "Thread:StableNetworkData"
|
||||
WPAN_THREAD_STABLE_NETWORK_DATA_VERSION = "Thread:StableNetworkDataVersion"
|
||||
WPAN_THREAD_PREFERRED_ROUTER_ID = "Thread:PreferredRouterID"
|
||||
WPAN_THREAD_COMMISSIONER_ENABLED = "Thread:Commissioner:Enabled"
|
||||
WPAN_THREAD_DEVICE_MODE = "Thread:DeviceMode"
|
||||
WPAN_THREAD_OFF_MESH_ROUTES = "Thread:OffMeshRoutes"
|
||||
WPAN_THREAD_ON_MESH_PREFIXES = "Thread:OnMeshPrefixes"
|
||||
WPAN_THREAD_ROUTER_ROLE_ENABLED = "Thread:RouterRole:Enabled"
|
||||
WPAN_THREAD_CONFIG_FILTER_RLOC_ADDRESSES = "Thread:Config:FilterRLOCAddresses"
|
||||
WPAN_THREAD_ACTIVE_DATASET = "Thread:ActiveDataset"
|
||||
WPAN_THREAD_ACTIVE_DATASET_ASVALMAP = "Thread:ActiveDataset:AsValMap"
|
||||
WPAN_THREAD_PENDING_DATASET = "Thread:PendingDataset"
|
||||
WPAN_THREAD_PENDING_DATASET_ASVALMAP = "Thread:PendingDataset:AsValMap"
|
||||
|
||||
WPAN_OT_LOG_LEVEL = "OpenThread:LogLevel"
|
||||
WPAN_OT_STEERING_DATA_ADDRESS = "OpenThread:SteeringData:Address"
|
||||
WPAN_OT_STEERING_DATA_SET_WHEN_JOINABLE = "OpenThread:SteeringData:SetWhenJoinable"
|
||||
WPAN_OT_MSG_BUFFER_COUNTERS = "OpenThread:MsgBufferCounters"
|
||||
WPAN_OT_MSG_BUFFER_COUNTERS_AS_STRING = "OpenThread:MsgBufferCounters:AsString"
|
||||
WPAN_OT_DEBUG_TEST_ASSERT = "OpenThread:Debug:TestAssert"
|
||||
WPAN_OT_DEBUG_TEST_WATCHDOG = "OpenThread:Debug:TestWatchdog"
|
||||
|
||||
WPAN_NCP_COUNTER_ALL_MAC = "NCP:Counter:AllMac"
|
||||
WPAN_NCP_COUNTER_ALL_MAC_ASVALMAP = "NCP:Counter:AllMac:AsValMap"
|
||||
|
||||
WPAN_MAC_WHITELIST_ENABLED = "MAC:Whitelist:Enabled"
|
||||
WPAN_MAC_WHITELIST_ENTRIES = "MAC:Whitelist:Entries"
|
||||
WPAN_MAC_WHITELIST_ENTRIES_ASVALMAP = "MAC:Whitelist:Entries:AsValMap"
|
||||
WPAN_MAC_BLACKLIST_ENABLED = "MAC:Blacklist:Enabled"
|
||||
WPAN_MAC_BLACKLIST_ENTRIES = "MAC:Blacklist:Entries"
|
||||
WPAN_MAC_BLACKLIST_ENTRIES_ASVALMAP = "MAC:Blacklist:Entries:AsValMap"
|
||||
|
||||
WPAN_JAM_DETECTION_STATUS = "JamDetection:Status"
|
||||
WPAN_JAM_DETECTION_ENABLE = "JamDetection:Enable"
|
||||
WPAN_JAM_DETECTION_RSSI_THRESHOLD = "JamDetection:RssiThreshold"
|
||||
WPAN_JAM_DETECTION_WINDOW = "JamDetection:Window"
|
||||
WPAN_JAM_DETECTION_BUSY_PERIOD = "JamDetection:BusyPeriod"
|
||||
WPAN_JAM_DETECTION_DEBUG_HISTORY_BITMAP = "JamDetection:Debug:HistoryBitmap"
|
||||
|
||||
WPAN_CHANNEL_MONITOR_SAMPLE_INTERVAL = "ChannelMonitor:SampleInterval"
|
||||
WPAN_CHANNEL_MONITOR_RSSI_THRESHOLD = "ChannelMonitor:RssiThreshold"
|
||||
WPAN_CHANNEL_MONITOR_SAMPLE_WINDOW = "ChannelMonitor:SampleWindow"
|
||||
WPAN_CHANNEL_MONITOR_SAMPLE_COUNT = "ChannelMonitor:SampleCount"
|
||||
WPAN_CHANNEL_MONITOR_CHANNEL_QUALITY = "ChannelMonitor:ChannelQuality"
|
||||
WPAN_CHANNEL_MONITOR_CHANNEL_QUALITY_ASVALMAP = "ChannelMonitor:ChannelQuality:AsValMap"
|
||||
|
||||
#----------------------------------------------------------------------------------------------------------------------
|
||||
# Valid state values
|
||||
|
||||
STATE_UNINITIALIZED = '"uninitialized"'
|
||||
STATE_FAULT = '"uninitialized:fault"'
|
||||
STATE_UPGRADING = '"uninitialized:upgrading"'
|
||||
STATE_DEEP_SLEEP = '"offline:deep-sleep"'
|
||||
STATE_OFFLINE = '"offline"'
|
||||
STATE_COMMISSIONED = '"offline:commissioned"'
|
||||
STATE_ASSOCIATING = '"associating"'
|
||||
STATE_CREDENTIALS_NEEDED = '"associating:credentials-needed"'
|
||||
STATE_ASSOCIATED = '"associated"'
|
||||
STATE_ISOLATED = '"associated:no-parent"'
|
||||
STATE_NETWAKE_ASLEEP = '"associated:netwake-asleep"'
|
||||
STATE_NETWAKE_WAKING = '"associated:netwake-waking"'
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Node types (from `WPAN_NODE_TYPE` property)
|
||||
|
||||
NODE_TYPE_UNKNOWN = '"unknown"'
|
||||
NODE_TYPE_LEADER = '"leader"'
|
||||
NODE_TYPE_ROUTER = '"router"'
|
||||
NODE_TYPE_END_DEVICE = '"end-device"'
|
||||
NODE_TYPE_SLEEPY_END_DEVICE = '"sleepy-end-device"'
|
||||
NODE_TYPE_COMMISSIONER = '"commissioner"'
|
||||
NODE_TYPE_NEST_LURKER = '"nl-lurker"'
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
# Node types used by `Node.join()`
|
||||
|
||||
JOIN_TYPE_ROUTER = 'r'
|
||||
JOIN_TYPE_END_DEVICE = 'e'
|
||||
JOIN_TYPE_SLEEPY_END_DEVICE = 's'
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def _log(text, new_line=True, flush=True):
|
||||
sys.stdout.write(text)
|
||||
if new_line:
|
||||
sys.stdout.write('\n')
|
||||
if flush:
|
||||
sys.stdout.flush()
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Node class
|
||||
|
||||
class Node(object):
|
||||
""" A wpantund OT NCP instance """
|
||||
|
||||
_VERBOSE = False # defines the default verbosity setting (can be changed per `Node`)
|
||||
|
||||
# path to `wpantund`, `wpanctl` and `ot-ncp-ftd` code
|
||||
_WPANTUND = '/usr/local/sbin/wpantund'
|
||||
_WPANCTL = '/usr/local/bin/wpanctl'
|
||||
_OT_NCP_FTD = '../../examples/apps/ncp/ot-ncp-ftd'
|
||||
|
||||
_TUND_LOG_TO_FILE = True # determines if the wpantund logs are saved in file or sent to stdout
|
||||
_TUND_LOG_FNAME = 'wpantund-logs' # name of wpantund log file (if # name of wpantund _TUND_LOG_TO_FILE is True)
|
||||
|
||||
# interface name
|
||||
_INTFC_NAME_PREFIX = 'utun' if sys.platform == 'darwin' else 'wpan'
|
||||
_START_INDEX = 4 if sys.platform == 'darwin' else 1
|
||||
|
||||
_cur_index = _START_INDEX
|
||||
_all_nodes = weakref.WeakSet()
|
||||
|
||||
def __init__(self, verbose=_VERBOSE):
|
||||
"""Creates a new `Node` instance"""
|
||||
|
||||
index = Node._cur_index
|
||||
Node._cur_index += 1
|
||||
|
||||
self._index = index
|
||||
self._interface_name = self._INTFC_NAME_PREFIX + str(index)
|
||||
self._verbose = verbose
|
||||
|
||||
cmd = self._WPANTUND + \
|
||||
' -o Config:NCP:SocketPath \"system:{} {}\"'.format(self._OT_NCP_FTD, index) + \
|
||||
' -o Config:TUN:InterfaceName {}'.format(self._interface_name) + \
|
||||
' -o Config:NCP:DriverName spinel' + \
|
||||
' -o Daemon:SyslogMask \"all -debug\"'
|
||||
|
||||
if Node._TUND_LOG_TO_FILE:
|
||||
self._tund_log_file = open(self._TUND_LOG_FNAME + str(index) + '.log', 'wb')
|
||||
else:
|
||||
self._tund_log_file = None
|
||||
|
||||
if self._verbose:
|
||||
_log('$ Node{}.__init__() cmd: {}'.format(index, cmd))
|
||||
|
||||
self._wpantund_process = subprocess.Popen(cmd, shell=True, stderr=self._tund_log_file)
|
||||
|
||||
self._wpanctl_cmd = self._WPANCTL + ' -I ' + self._interface_name + ' '
|
||||
|
||||
self._recvers = weakref.WeakValueDictionary() # map from local_port to `AsyncReceiver` object
|
||||
Node._all_nodes.add(self)
|
||||
|
||||
def __del__(self):
|
||||
self._wpantund_process.terminate()
|
||||
self._tund_log_file.close()
|
||||
|
||||
def __repr__(self):
|
||||
return 'Node (index={}, interface_name={})'.format(self._index, self._interface_name)
|
||||
|
||||
@property
|
||||
def index(self):
|
||||
return self._index
|
||||
|
||||
@property
|
||||
def interface_name(self):
|
||||
return self._interface_name
|
||||
|
||||
@property
|
||||
def tund_log_file(self):
|
||||
return self._tund_log_file
|
||||
|
||||
#------------------------------------------------------------------------------------------------------------------
|
||||
# Executing a `wpanctl` command
|
||||
|
||||
def wpanctl(self, cmd):
|
||||
""" Runs a wpanctl command on the given wpantund/OT-NCP instance and returns the output """
|
||||
|
||||
if self._verbose:
|
||||
_log('$ Node{}.wpanctl(\'{}\')'.format(self._index, cmd), new_line=False)
|
||||
|
||||
result = subprocess.check_output(self._wpanctl_cmd + cmd, shell=True, stderr=subprocess.STDOUT)
|
||||
|
||||
if len(result) >= 1 and result[-1] == '\n': # remove the last char if it is '\n',
|
||||
result = result[:-1]
|
||||
|
||||
if self._verbose:
|
||||
if '\n' in result:
|
||||
_log(':')
|
||||
for line in result.splitlines():
|
||||
_log(' ' + line)
|
||||
else:
|
||||
_log(' -> \'{}\''.format(result))
|
||||
|
||||
return result
|
||||
|
||||
#------------------------------------------------------------------------------------------------------------------
|
||||
# APIs matching `wpanctl` commands.
|
||||
|
||||
def get(self, prop_name, value_only=True):
|
||||
return self.wpanctl('get ' + ('-v ' if value_only else '') + prop_name)
|
||||
|
||||
def set(self, prop_name, value, binary_data=False):
|
||||
return self._update_prop('set', prop_name, value, binary_data)
|
||||
|
||||
def add(self, prop_name, value, binary_data=False):
|
||||
return self._update_prop('add', prop_name, value, binary_data)
|
||||
|
||||
def remove(self, prop_name, value, binary_data=False):
|
||||
return self._update_prop('remove', prop_name, value, binary_data)
|
||||
|
||||
def _update_prop(self, action, prop_name, value, binary_data):
|
||||
return self.wpanctl(action + ' ' + prop_name + ' ' + ('-d ' if binary_data else '') +
|
||||
'-v ' + value) # use -v to handle values starting with `-`.
|
||||
|
||||
def reset(self):
|
||||
return self.wpanctl('reset')
|
||||
|
||||
def status(self):
|
||||
return self.wpanctl('status')
|
||||
|
||||
def leave(self):
|
||||
return self.wpanctl('leave')
|
||||
|
||||
def form(self, name, channel=None, node_type=None):
|
||||
return self.wpanctl('form \"' + name + '\"' +
|
||||
(' -c {}'.format(channel) if channel is not None else '') +
|
||||
(' -T {}'.format(node_type) if node_type is not None else ''))
|
||||
|
||||
def join(self, name, channel=None, node_type=None, panid=None, xpanid=None):
|
||||
return self.wpanctl('join \"' + name + '\"' +
|
||||
(' -c {}'.format(channel) if channel is not None else '') +
|
||||
(' -T {}'.format(node_type) if node_type is not None else '') +
|
||||
(' -p {}'.format(panid) if panid is not None else '') +
|
||||
(' -x {}'.format(xpanid) if xpanid is not None else ''))
|
||||
|
||||
def active_scan(self, channel=None):
|
||||
return self.wpanctl('scan' +
|
||||
(' -c {}'.format(channel) if channel is not None else ''))
|
||||
|
||||
def energy_scan(self, channel=None):
|
||||
return self.wpanctl('scan -e' +
|
||||
(' -c {}'.format(channel) if channel is not None else ''))
|
||||
|
||||
def discover_scan(self, channel=None, joiner_only=False, enable_filtering=False, panid_filter=None):
|
||||
return self.wpanctl('scan -d' +
|
||||
(' -c {}'.format(channel) if channel is not None else '') +
|
||||
(' -j' if joiner_only else '') +
|
||||
(' -e' if enable_filtering else '') +
|
||||
(' -p {}'.format(panid_filter) if panid_filter is not None else ''))
|
||||
|
||||
def permit_join(self, duration_sec=None, port=None, udp=True, tcp=True):
|
||||
if not udp and not tcp: # incorrect use!
|
||||
return ''
|
||||
traffic_type = ''
|
||||
if udp and not tcp:
|
||||
traffic_type = ' --udp'
|
||||
if tcp and not udp:
|
||||
traffic_type = ' --tcp'
|
||||
if port is not None and duration_sec is None:
|
||||
duration_sec = '240'
|
||||
|
||||
return self.wpanctl('permit-join' +
|
||||
(' {}'.format(duration_sec) if duration_sec is not None else '') +
|
||||
(' {}'.format(port) if port is not None else '') +
|
||||
traffic_type)
|
||||
|
||||
def config_gateway(self, prefix, default_route=False):
|
||||
return self.wpanctl('config-gateway ' + prefix +
|
||||
(' -d' if default_route else ''))
|
||||
|
||||
def add_route(self, route_prefix, prefix_len_in_bytes=None, priority=None):
|
||||
"""route priority [(>0 for high, 0 for medium, <0 for low)]"""
|
||||
return self.wpanctl('add-route ' + route_prefix +
|
||||
(' -l {}'.format(prefix_len_in_bytes) if prefix_len_in_bytes is not None else '') +
|
||||
(' -p {}'.format(priority) if priority is not None else ''))
|
||||
|
||||
def remove_route(self, route_prefix, prefix_len_in_bytes=None, priority=None):
|
||||
"""route priority [(>0 for high, 0 for medium, <0 for low)]"""
|
||||
return self.wpanctl('remove-route ' + route_prefix +
|
||||
(' -l {}'.format(prefix_len_in_bytes) if prefix_len_in_bytes is not None else '') +
|
||||
(' -p {}'.format(priority) if priority is not None else ''))
|
||||
|
||||
#------------------------------------------------------------------------------------------------------------------
|
||||
# Helper methods
|
||||
|
||||
def is_associated(self):
|
||||
return self.get(WPAN_STATE) == STATE_ASSOCIATED
|
||||
|
||||
def join_node(self, node, node_type=JOIN_TYPE_ROUTER):
|
||||
"""Join a network specified by another node, `node` should be a Node"""
|
||||
|
||||
if not node.is_associated():
|
||||
return "{} is not associated".format(node)
|
||||
|
||||
name = node.get(WPAN_NAME)
|
||||
panid = node.get(WPAN_PANID)
|
||||
xpanid = node.get(WPAN_XPANID)
|
||||
netkey = node.get(WPAN_KEY)
|
||||
channel = node.get(WPAN_CHANNEL)
|
||||
|
||||
self.set(WPAN_KEY, netkey[1:-1], binary_data=True)
|
||||
return self.join(name[1:-1], channel=channel, node_type=node_type, panid=panid, xpanid=xpanid)
|
||||
|
||||
def whitelist_node(self, node):
|
||||
"""Adds a given node (of type `Node`) to the whitelist of `self` and enables whitelisting on `self`"""
|
||||
|
||||
self.add(WPAN_MAC_WHITELIST_ENTRIES, node.get(WPAN_EXT_ADDRESS)[1:-1])
|
||||
self.set(WPAN_MAC_WHITELIST_ENABLED, '1')
|
||||
|
||||
def is_in_scan_result(self, scan_result):
|
||||
"""Checks if node is in the scan results
|
||||
`scan_result` must be an array of `ScanResult` object (see `parse_scan_result`).
|
||||
"""
|
||||
joinable = (self.get(WPAN_NETWORK_ALLOW_JOIN) == 'true')
|
||||
panid = self.get(WPAN_PANID)
|
||||
xpanid = self.get(WPAN_XPANID)[2:]
|
||||
name = self.get(WPAN_NAME)[1:-1]
|
||||
channel = self.get(WPAN_CHANNEL)
|
||||
ext_address = self.get(WPAN_EXT_ADDRESS)[1:-1]
|
||||
|
||||
for item in scan_result:
|
||||
if all( [item.network_name == name,
|
||||
item.panid == panid,
|
||||
item.xpanid == xpanid,
|
||||
item.channel == channel,
|
||||
item.ext_address == ext_address,
|
||||
(item.type == ScanResult.TYPE_DISCOVERY_SCAN) or (item.joinable == joinable) ] ):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
#------------------------------------------------------------------------------------------------------------------
|
||||
# class methods
|
||||
|
||||
@classmethod
|
||||
def init_all_nodes(cls, wait_time=20):
|
||||
"""Issues a `wpanctl.leave` on all `Node` objects and waits for them to be ready"""
|
||||
random.seed(12345)
|
||||
time.sleep(0.5)
|
||||
start_time = time.time()
|
||||
for node in Node._all_nodes:
|
||||
while True:
|
||||
try:
|
||||
node.leave()
|
||||
except subprocess.CalledProcessError as e:
|
||||
_log(' -> \'{}\' exit code: {}'.format(e.output, e.returncode))
|
||||
if time.time() - start_time > wait_time:
|
||||
print 'Took too long to init all nodes ({}>{} sec)'.format(time.time() - start_time, wait_time)
|
||||
raise
|
||||
except:
|
||||
raise
|
||||
else:
|
||||
break
|
||||
time.sleep(0.1)
|
||||
|
||||
#------------------------------------------------------------------------------------------------------------------
|
||||
# IPv6 message Sender and Receiver class
|
||||
|
||||
class _NodeError(BaseException):
|
||||
pass
|
||||
|
||||
def prepare_tx(self, src, dst, data=40, count=1):
|
||||
"""Prepares an IPv6 msg transmission.
|
||||
|
||||
- `src` and `dst` can be either a string containing IPv6 address, or a tuple (ipv6 address as string, port),
|
||||
if no port is given, a random port number is used.
|
||||
- `data` can be either a string containing the message to be sent, or an int indicating size of the message (a
|
||||
random message with the given length will be used).
|
||||
- `count` gives number of times the message will be sent (default is 1).
|
||||
|
||||
Returns an `AsyncSender` object.
|
||||
|
||||
"""
|
||||
if isinstance(src, tuple):
|
||||
src_addr = src[0]
|
||||
src_port = src[1]
|
||||
else:
|
||||
src_addr = src
|
||||
src_port = random.randint(49152, 65535)
|
||||
|
||||
if isinstance(dst, tuple):
|
||||
dst_addr = dst[0]
|
||||
dst_port = dst[1]
|
||||
else:
|
||||
dst_addr = dst
|
||||
dst_port = random.randint(49152, 65535)
|
||||
|
||||
if isinstance(data, int):
|
||||
# create a random message with the given length.
|
||||
all_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.,><?;:[]=-+)(*&^%$#@'
|
||||
msg = ''.join(random.choice(all_chars) for _ in range(data))
|
||||
else:
|
||||
msg = data
|
||||
|
||||
return AsyncSender(self, src_addr, src_port, dst_addr, dst_port, msg, count)
|
||||
|
||||
def prepare_rx(self, sender):
|
||||
"""Prepare to receive messages from a sender (an `AsyncSender`)"""
|
||||
local_port = sender.dst_port
|
||||
|
||||
if local_port in self._recvers:
|
||||
receiver = self._recvers[local_port]
|
||||
else:
|
||||
receiver = AsyncReceiver(self, local_port)
|
||||
self._recvers[local_port] = receiver
|
||||
|
||||
receiver._add_sender(sender.src_addr, sender.src_port, sender.msg, sender.count)
|
||||
return receiver
|
||||
|
||||
def _remove_recver(self, recvr):
|
||||
# Removes a receiver from weak dictionary - called when the receiver is done and its scoket is closed
|
||||
local_port = recvr.local_port
|
||||
if local_port in self._recvers:
|
||||
del self._recvers[local_port]
|
||||
|
||||
@staticmethod
|
||||
def perform_async_tx_rx(timeout=20):
|
||||
"""Called to perform all previously prepared async rx and tx operations"""
|
||||
try:
|
||||
start_time = time.time()
|
||||
while asyncore.socket_map:
|
||||
elapsed_time = time.time() - start_time
|
||||
if elapsed_time > timeout:
|
||||
print 'Performing aysnc tx/tx took too long ({}>{} sec)'.format(elapsed_time, timeout)
|
||||
raise Node._NodeError('perform_tx_rx timed out ({}>{} sec)'.format(elapsed_time, timeout))
|
||||
# perform a single asyncore loop
|
||||
asyncore.loop(timeout=1, count=1)
|
||||
except:
|
||||
print 'Failed to perform async rx/tx'
|
||||
raise
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# `AsyncSender` and `AsyncReceiver classes
|
||||
|
||||
_SO_BINDTODEVICE = 25
|
||||
|
||||
def _is_ipv6_addr_link_local(ip_addr):
|
||||
"""Indicates if a given IPv6 address is link-local"""
|
||||
return ip_addr.lower().startswith('fe80::')
|
||||
|
||||
def _create_socket_address(ip_address, port):
|
||||
"""Convert a given IPv6 address (string) and port number into a socket address"""
|
||||
# `socket.getaddrinfo() returns a list of `(family, socktype, proto, canonname, sockaddr)` where `sockaddr`
|
||||
# (at index 4) can be used as input in socket methods (like `sendto()`, `bind()`, etc.).
|
||||
return socket.getaddrinfo(ip_address, port)[0][4]
|
||||
|
||||
class AsyncSender(asyncore.dispatcher):
|
||||
""" An IPv6 async message sender - use `Node.prepare_tx()` to create one"""
|
||||
|
||||
def __init__(self, node, src_addr, src_port, dst_addr, dst_port, msg, count):
|
||||
self._node = node
|
||||
self._src_addr = src_addr
|
||||
self._src_port = src_port
|
||||
self._dst_addr = dst_addr
|
||||
self._dst_port = dst_port
|
||||
self._msg = msg
|
||||
self._count = count
|
||||
self._dst_sock_addr = _create_socket_address(dst_addr, dst_port)
|
||||
self._tx_buffer = self._msg
|
||||
self._tx_counter = 0
|
||||
|
||||
# Create a socket, bind it to the node's interface
|
||||
sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
sock.setsockopt(socket.SOL_SOCKET, _SO_BINDTODEVICE, node.interface_name + '\0')
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
|
||||
|
||||
# Bind the socket to the given src address
|
||||
if _is_ipv6_addr_link_local(src_addr):
|
||||
# If src is a link local address it requires the interface name to be specified.
|
||||
src_sock_addr = _create_socket_address(src_addr + '%' + node.interface_name, src_port)
|
||||
else:
|
||||
src_sock_addr = _create_socket_address(src_addr, src_port)
|
||||
sock.bind(src_sock_addr)
|
||||
|
||||
asyncore.dispatcher.__init__(self, sock)
|
||||
|
||||
# Property getters
|
||||
|
||||
@property
|
||||
def node(self):
|
||||
return self._node
|
||||
|
||||
@property
|
||||
def src_addr(self):
|
||||
return self._src_addr
|
||||
|
||||
@property
|
||||
def src_port(self):
|
||||
return self._src_port
|
||||
|
||||
@property
|
||||
def dst_addr(self):
|
||||
return self._dst_addr
|
||||
|
||||
@property
|
||||
def dst_port(self):
|
||||
return self._dst_port
|
||||
|
||||
@property
|
||||
def msg(self):
|
||||
return self._msg
|
||||
|
||||
@property
|
||||
def count(self):
|
||||
return self._count
|
||||
|
||||
@property
|
||||
def was_successful(self):
|
||||
"""Indicates if the transmission of IPv6 messages finished successfully"""
|
||||
return self._tx_counter == self._count
|
||||
|
||||
# asyncore.dispatcher callbacks
|
||||
|
||||
def readable(self):
|
||||
return False
|
||||
|
||||
def writable(self):
|
||||
return True
|
||||
|
||||
def handle_write(self):
|
||||
sent_len = self.sendto(self._tx_buffer, self._dst_sock_addr)
|
||||
|
||||
if self._node._verbose:
|
||||
if sent_len < 30:
|
||||
info_text = '{} bytes ("{}")'.format(sent_len, self._tx_buffer[:sent_len])
|
||||
else:
|
||||
info_text = '{} bytes'.format(sent_len)
|
||||
_log('- Node{} sent {} to [{}]:{} from [{}]:{}'.format(self._node._index, info_text,
|
||||
self._dst_addr, self._dst_port,
|
||||
self._src_addr, self._src_port))
|
||||
|
||||
self._tx_buffer = self._tx_buffer[sent_len:]
|
||||
|
||||
if len(self._tx_buffer) == 0:
|
||||
self._tx_counter += 1
|
||||
if self._tx_counter < self._count:
|
||||
self._tx_buffer = self._msg
|
||||
else:
|
||||
self.handle_close()
|
||||
|
||||
def handle_close(self):
|
||||
self.close()
|
||||
|
||||
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
class AsyncReceiver(asyncore.dispatcher):
|
||||
""" An IPv6 async message receiver - use `prepare_tx()` to create one"""
|
||||
|
||||
_MAX_RECV_SIZE = 2048
|
||||
|
||||
class _SenderInfo(object):
|
||||
def __init__(self, sender_addr, sender_port, msg, count):
|
||||
self._sender_addr = sender_addr
|
||||
self._sender_port = sender_port
|
||||
self._msg = msg
|
||||
self._count = count
|
||||
self._rx_counter = 0
|
||||
|
||||
def _check_received(self, msg, sender_addr, sender_port):
|
||||
if self._msg == msg and self._sender_addr == sender_addr and self._sender_port == sender_port:
|
||||
self._rx_counter += 1
|
||||
return self._did_recv_all()
|
||||
|
||||
def _did_recv_all(self):
|
||||
return self._rx_counter >= self._count
|
||||
|
||||
def __init__(self, node, local_port):
|
||||
self._node = node
|
||||
self._local_port = local_port
|
||||
self._senders = [] # list of `_SenderInfo` objects
|
||||
self._all_rx = [] # contains all received messages as a list of (pkt, (src_addr, src_port))
|
||||
|
||||
# Create a socket, bind it to the node's interface
|
||||
sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
sock.setsockopt(socket.SOL_SOCKET, _SO_BINDTODEVICE, node.interface_name + '\0')
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
|
||||
|
||||
# Bind the socket to any IPv6 address with the given local port
|
||||
local_sock_addr = _create_socket_address('::', local_port)
|
||||
sock.bind(local_sock_addr)
|
||||
|
||||
asyncore.dispatcher.__init__(self, sock)
|
||||
|
||||
def _add_sender(self, sender_addr, sender_port, msg, count):
|
||||
self._senders.append(AsyncReceiver._SenderInfo(sender_addr, sender_port, msg, count))
|
||||
|
||||
# Property getters
|
||||
|
||||
@property
|
||||
def node(self):
|
||||
return self._node
|
||||
|
||||
@property
|
||||
def local_port(self):
|
||||
return self._local_port
|
||||
|
||||
@property
|
||||
def all_rx_msg(self):
|
||||
"""returns all received messages as a list of (msg, (src_addr, src_port))"""
|
||||
return self._all_rx
|
||||
|
||||
@property
|
||||
def was_successful(self):
|
||||
"""Indicates if all expected IPv6 messages were received successfully"""
|
||||
return all([sender._did_recv_all() for sender in self._senders])
|
||||
|
||||
# asyncore.dispatcher callbacks
|
||||
|
||||
def readable(self):
|
||||
return True
|
||||
|
||||
def writable(self):
|
||||
return False
|
||||
|
||||
def handle_read(self):
|
||||
(msg, src_sock_addr) = self.recvfrom(AsyncReceiver._MAX_RECV_SIZE)
|
||||
src_addr = src_sock_addr[0]
|
||||
src_port = src_sock_addr[1]
|
||||
|
||||
if (_is_ipv6_addr_link_local(src_addr)):
|
||||
if '%' in src_addr:
|
||||
src_addr = src_addr.split('%')[0] # remove the interface name from address
|
||||
|
||||
if self._node._verbose:
|
||||
if len(msg) < 30:
|
||||
info_text = '{} bytes ("{}")'.format(len(msg), msg)
|
||||
else:
|
||||
info_text = '{} bytes'.format(len(msg))
|
||||
_log('- Node{} received {} on port {} from [{}]:{}'.format(self._node._index, info_text,
|
||||
self._local_port,
|
||||
src_addr, src_port))
|
||||
|
||||
self._all_rx.append((msg, (src_addr, src_port)))
|
||||
|
||||
if all([sender._check_received(msg, src_addr, src_port) for sender in self._senders]):
|
||||
self.handle_close()
|
||||
|
||||
def handle_close(self):
|
||||
self.close()
|
||||
# remove the receiver from the node once the socket is closed
|
||||
self._node._remove_recver(self)
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def verify(condition):
|
||||
if not condition:
|
||||
print 'verify() failed'
|
||||
exit(1)
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# Parsing `wpanctl` output
|
||||
|
||||
class ScanResult(object):
|
||||
""" This object encapsulates a scan result (active/discover/energy scan)"""
|
||||
|
||||
TYPE_ACTIVE_SCAN = 'active-scan'
|
||||
TYPE_DISCOVERY_SCAN = 'discover-scan'
|
||||
TYPE_ENERGY_SCAN = 'energy-scan'
|
||||
|
||||
def __init__(self, result_text):
|
||||
|
||||
items = [item.strip() for item in result_text.split('|')]
|
||||
|
||||
if len(items) == 8:
|
||||
self._type = ScanResult.TYPE_ACTIVE_SCAN
|
||||
self._index = items[0]
|
||||
self._joinable = (items[1] == 'YES')
|
||||
self._network_name = items[2][1:-1]
|
||||
self._panid = items[3]
|
||||
self._channel = items[4]
|
||||
self._xpanid = items[5]
|
||||
self._ext_address = items[6]
|
||||
self._rssi = items[7]
|
||||
elif len(items) == 7:
|
||||
self._type = ScanResult.TYPE_DISCOVERY_SCAN
|
||||
self._index = items[0]
|
||||
self._network_name = items[1][1:-1]
|
||||
self._panid = items[2]
|
||||
self._channel = items[3]
|
||||
self._xpanid = items[4]
|
||||
self._ext_address = items[5]
|
||||
self._rssi = items[6]
|
||||
elif len(items) == 2:
|
||||
self._type = ScanResult.TYPE_ENERGY_SCAN
|
||||
self._channel = items[0]
|
||||
self._rssi = items[1]
|
||||
else:
|
||||
raise ValueError('"{}" does not seem to be a valid scan result string'.result_text)
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return self._type
|
||||
|
||||
@property
|
||||
def joinable(self):
|
||||
return self._joinable
|
||||
|
||||
@property
|
||||
def network_name(self):
|
||||
return self._network_name
|
||||
|
||||
@property
|
||||
def panid(self):
|
||||
return self._panid
|
||||
|
||||
@property
|
||||
def channel(self):
|
||||
return self._channel
|
||||
|
||||
@property
|
||||
def xpanid(self):
|
||||
return self._xpanid
|
||||
|
||||
@property
|
||||
def ext_address(self):
|
||||
return self._ext_address
|
||||
|
||||
@property
|
||||
def rssi(self):
|
||||
return self._rssi
|
||||
|
||||
def __repr__(self):
|
||||
return 'ScanResult({})'.format(self.__dict__)
|
||||
|
||||
def parse_scan_result(scan_result):
|
||||
""" Parses scan result string and returns an array of `ScanResult` objects"""
|
||||
return [ ScanResult(item) for item in scan_result.split('\n')[2:] ] # skip first two lines which are table headers
|
||||
|
||||
|
||||
Reference in New Issue
Block a user