From 12a7060a85e378269c38197ef782089fdced169b Mon Sep 17 00:00:00 2001 From: Robert Quattlebaum Date: Thu, 19 May 2016 17:46:16 -0700 Subject: [PATCH] ncp: Network Control Processor (NCP) implementation (#31) (#45) In addition to the "SoC/CLI" implementation that is already included, OpenThread needs a Network Control Processor (NCP) implementation to facilitate applications where the Host Controller is logically separate from the controller that implements the Thread stack (Which is the NCP). This task also necessitates the definition of the protocol that the Host Controller and the NCP use to communicate with each other. This commit represents a functional starting point for both of these. The initial name of the line protocol is "Spinel" (Named after the [gemstone][1]). The long-term goal is to standardize the Spinel protocol to be the official line protocol for host-to-NCP comunication for Thread. A [draft of the Spinel protocol document][2] is included in this commit. At this point everything is subject to change, but as-is the protocol has been demonstrated to work fairly well. The NCP implementation in the has been tested and is working well enough for basic connectivity, but it is far from complete. A Host implementation is not yet provided in the tree, but will eventually be included. A separate host implementation for Linux has been written and is anticipated to be released as a separate project over the next few weeks. [1]: https://en.wikipedia.org/wiki/Spinel [2]: https://github.com/openthread/openthread/blob/feature/ncp-spinel/src/ncp/PROTOCOL.md#spinel-host-controller-interface --- configure.ac | 2 + examples/Makefile.am | 2 + examples/ncp/Makefile.am | 53 ++ examples/ncp/main.cpp | 78 ++ src/Makefile.am | 2 + src/core/openthread.cpp | 7 +- src/ncp/Makefile.am | 82 ++ src/ncp/PROTOCOL.md | 1235 ++++++++++++++++++++++++++++++ src/ncp/flen.cpp | 168 ++++ src/ncp/flen.hpp | 162 ++++ src/ncp/hdlc.cpp | 266 +++++++ src/ncp/hdlc.hpp | 161 ++++ src/ncp/ncp.cpp | 204 +++++ src/ncp/ncp.hpp | 79 ++ src/ncp/ncp_base.cpp | 1572 ++++++++++++++++++++++++++++++++++++++ src/ncp/ncp_base.hpp | 117 +++ src/ncp/spinel.c | 1123 +++++++++++++++++++++++++++ src/ncp/spinel.h | 415 ++++++++++ 18 files changed, 5726 insertions(+), 2 deletions(-) create mode 100644 examples/ncp/Makefile.am create mode 100644 examples/ncp/main.cpp create mode 100644 src/ncp/Makefile.am create mode 100644 src/ncp/PROTOCOL.md create mode 100644 src/ncp/flen.cpp create mode 100644 src/ncp/flen.hpp create mode 100644 src/ncp/hdlc.cpp create mode 100644 src/ncp/hdlc.hpp create mode 100644 src/ncp/ncp.cpp create mode 100644 src/ncp/ncp.hpp create mode 100644 src/ncp/ncp_base.cpp create mode 100644 src/ncp/ncp_base.hpp create mode 100644 src/ncp/spinel.c create mode 100644 src/ncp/spinel.h diff --git a/configure.ac b/configure.ac index 042a4c4eb..aa7e592d1 100644 --- a/configure.ac +++ b/configure.ac @@ -429,6 +429,7 @@ include/crypto/Makefile include/platform/Makefile src/Makefile src/cli/Makefile +src/ncp/Makefile src/core/Makefile third_party/Makefile third_party/mbedtls/Makefile @@ -436,6 +437,7 @@ examples/Makefile examples/platform/Makefile examples/platform/posix/Makefile examples/cli/Makefile +examples/ncp/Makefile tests/Makefile tests/scripts/Makefile tests/unit/Makefile diff --git a/examples/Makefile.am b/examples/Makefile.am index 213b6232f..0fac8e3a0 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -33,6 +33,7 @@ include $(abs_top_nlbuild_autotools_dir)/automake/pre.am DIST_SUBDIRS = \ platform \ cli \ + ncp \ $(NULL) # Always build (e.g. for 'make all') these subdirectories. @@ -40,6 +41,7 @@ DIST_SUBDIRS = \ SUBDIRS = \ platform \ cli \ + ncp \ $(NULL) # Always pretty (e.g. for 'make pretty') these subdirectories. diff --git a/examples/ncp/Makefile.am b/examples/ncp/Makefile.am new file mode 100644 index 000000000..e495eacae --- /dev/null +++ b/examples/ncp/Makefile.am @@ -0,0 +1,53 @@ +# +# Copyright (c) 2016, Nest Labs, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +include $(abs_top_nlbuild_autotools_dir)/automake/pre.am + +bin_PROGRAMS = ncp + +ncp_CPPFLAGS = \ + -I$(top_srcdir)/include \ + -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/core \ + -I$(top_srcdir)/examples \ + -I$(top_srcdir)/examples/platform/posix \ + -I$(top_srcdir)/third_party \ + $(OPENTHREAD_TARGET_DEFINES) \ + $(NULL) + +ncp_LDADD = \ + $(top_builddir)/src/ncp/libopenthread-ncp.a \ + $(top_builddir)/src/core/libopenthread.a \ + $(top_builddir)/examples/platform/posix/libopenthread-posix.a \ + $(top_builddir)/third_party/mbedtls/libmbedcrypto.a \ + -lpthread \ + $(NULL) + +ncp_SOURCES = main.cpp + +include $(abs_top_nlbuild_autotools_dir)/automake/post.am diff --git a/examples/ncp/main.cpp b/examples/ncp/main.cpp new file mode 100644 index 000000000..65a0dd67a --- /dev/null +++ b/examples/ncp/main.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2016, Nest Labs, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include + +#include +#include +#include + +struct gengetopt_args_info args_info; + +Thread::Ncp sNcp; + +void otSignalTaskletPending(void) +{ +} + +int main(int argc, char *argv[]) +{ + uint32_t atomic_state; + + memset(&args_info, 0, sizeof(args_info)); + + if (cmdline_parser(argc, argv, &args_info) != 0) + { + exit(1); + } + + hwAlarmInit(); + hwRadioInit(); + hwRandomInit(); + + otInit(); + + sNcp.Start(); + + while (1) + { + otProcessNextTasklet(); + + atomic_state = otPlatAtomicBegin(); + + if (!otAreTaskletsPending()) + { + hwSleep(); + } + + otPlatAtomicEnd(atomic_state); + } + + return 0; +} diff --git a/src/Makefile.am b/src/Makefile.am index 6b43f3ce2..d089b8cf2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -33,6 +33,7 @@ include $(abs_top_nlbuild_autotools_dir)/automake/pre.am DIST_SUBDIRS = \ cli \ core \ + ncp \ $(NULL) # Always build (e.g. for 'make all') these subdirectories. @@ -40,6 +41,7 @@ DIST_SUBDIRS = \ SUBDIRS = \ cli \ core \ + ncp \ $(NULL) # Always pretty (e.g. for 'make pretty') these subdirectories. diff --git a/src/core/openthread.cpp b/src/core/openthread.cpp index d35f927a4..1fcb4beb4 100644 --- a/src/core/openthread.cpp +++ b/src/core/openthread.cpp @@ -45,6 +45,11 @@ namespace Thread { +// This needs to not be static until the NCP +// the OpenThread API is capable enough for +// of of the features in the NCP. +ThreadNetif *sThreadNetif; + #ifdef __cplusplus extern "C" { #endif @@ -55,8 +60,6 @@ extern "C" { static otDEFINE_ALIGNED_VAR(sThreadNetifRaw, sizeof(ThreadNetif), uint64_t); -static ThreadNetif *sThreadNetif; - static void HandleActiveScanResult(void *aContext, Mac::Frame *aFrame); void otInit() diff --git a/src/ncp/Makefile.am b/src/ncp/Makefile.am new file mode 100644 index 000000000..a4c09d869 --- /dev/null +++ b/src/ncp/Makefile.am @@ -0,0 +1,82 @@ +# +# Copyright (c) 2016, Nest Labs, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +include $(abs_top_nlbuild_autotools_dir)/automake/pre.am + +EXTRA_DIST = \ + PROTOCOL.md \ + $(NULL) + +# Pull in the sources that comprise the OpenThread NCP library. + +lib_LIBRARIES = libopenthread-ncp.a + +libopenthread_ncp_a_CPPFLAGS = \ + -I$(top_srcdir)/include \ + -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/core \ + -I$(top_srcdir)/third_party \ + $(OPENTHREAD_TARGET_DEFINES) \ + $(NULL) + +libopenthread_ncp_a_CXXFLAGS = \ + -I$(top_srcdir)/include \ + -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/core \ + -I$(top_srcdir)/third_party \ + $(OPENTHREAD_TARGET_DEFINES) \ + $(NULL) + +libopenthread_ncp_a_SOURCES = \ + hdlc.cpp \ + hdlc.hpp \ + flen.cpp \ + flen.hpp \ + ncp.cpp \ + ncp.hpp \ + ncp_base.cpp \ + ncp_base.hpp \ + spinel.c spinel.h \ + $(NULL) + +include_HEADERS = \ + $(NULL) + +noinst_PROGRAMS = spinel-test +spinel_test_SOURCES = spinel.c +spinel_test_CFLAGS = -DSPINEL_SELF_TEST=1 + +TESTS = spinel-test + +install-headers: install-includeHEADERS + +if OPENTHREAD_BUILD_COVERAGE +CLEANFILES = $(wildcard *.gcda *.gcno) +endif # OPENTHREAD_BUILD_COVERAGE + +include $(abs_top_nlbuild_autotools_dir)/automake/post.am diff --git a/src/ncp/PROTOCOL.md b/src/ncp/PROTOCOL.md new file mode 100644 index 000000000..56f0959c9 --- /dev/null +++ b/src/ncp/PROTOCOL.md @@ -0,0 +1,1235 @@ +Spinel Host Controller Interface +================================ + +Updated: 2016-05-18 + +Written by: Robert Quattlebaum + +THIS DOCUMENT IS A WORK IN PROGRESS AND SUBJECT TO CHANGE. + +Copyright (c) 2016 Nest Labs, All Rights Reserved + +## 0. Abstract ## + +This document describes a general management protocol for enabling a host +device to communicate with and manage a network co-processor(NCP). + +While initially designed to support Thread-based NCPs, the NCP protocol +has been designed with a layered approach that allows it to be easily +adapted to other network protocols. + +## 1. Definitions ## + + * **NCP**: Network Control Processor + * **Host**: Computer or Micro-controller which controls the NCP. + * **TID**: Transaction Identifier (0-15) + * **IID**: Interface Identifier (0-3) + +## 2. Introduction ## + +This Network Co-Processor (NCP) protocol was designed to enable a host +device to communicate with and manage a NCP while also achieving the +following goals: + + * Adopt a layered approach to the protocol design, allowing future + support for other network protocols. + * Minimize the number of required commands/methods by providing a + rich, property-based API. + * Support NCPs capable of being connected to more than one network + at a time. + * Gracefully handle the addition of new features and capabilities + without necessarily breaking backward compatibility. + * Be as minimal and light-weight as possible without unnecessarily + sacrificing flexibility. + +On top of this core framework, we define the properties and commands +to enable various features and network protocols. + +## 3. Frame Format ## + +A frame is defined simply as the concatenation of + + * A header byte + * A command (up to three bytes) + * An optional command payload + + FRAME = HEADER CMD [CMD_PAYLOAD] + +Octets: | 1 | 1-3 | *n* +--------|--------|-----|----------------- +Fields: | HEADER | CMD | *[CMD_PAYLOAD]* + + +### 3.1. Header Format ### + +The header byte is broken down as follows: + + 0 1 2 3 4 5 6 7 + +-+-+-+-+-+-+-+-+ + |1|R|IID| TID | + +-+-+-+-+-+-+-+-+ + +#### 3.1.1. Flag Bit #### + +The most significant header bit is always set to 1 to allow this +protocol to be line compatible with BTLE HCI. By setting the first +bit, we can disambiguate between Spinel frames and HCI frames (which +always start with either `0x01` or `0x04`) without any additional +framing overhead. + +#### 3.1.2. Reserved Bit #### + +The reserved bit (`R`) is reserved for future use. The sender MUST +set this bit to zero, and the receiver MUST ignore frames with this +bit set. + +#### 3.1.3. Interface Identifier (IID) #### + +The Interface Identifier (IID) is a number between 0 and 3 which +identifies which subinterface the frame is intended for. This allows +the protocol to support connecting to more than one network at once. +The first subinterface (0) is considered the primary subinterface and +MUST be supported. Support for all other subinterfaces is OPTIONAL. + +#### 3.1.4. Transaction Identifier (TID) #### + +The least significant bits of the header represent the Transaction +Identifier(TID). The TID is used for correlating responses to the +commands which generated them. + +When a command is sent from the host, any reply to that command sent +by the NCP will use the same value for the TID. When the host receives +a frame that matches the TID of the command it sent, it can easily +recognize that frame as the actual response to that command. + +The TID value of zero (0) is used for commands to which a correlated +response is not expected or needed, such as for unsolicited update +commands sent to the host from the NCP. + +#### 3.2. Command Identifier (CMD) #### + +The command identifier is a 21-bit unsigned integer encoded in up to +three bytes using the packed unsigned integer format described in +section 7.2. This encoding allows for up to 2,097,152 individual +commands, with the first 127 commands represented as a single byte. +Command identifiers larger than 2,097,151 are explicitly forbidden. + +CID Range | Description +----------------------|------------------ +0 - 63 | Reserved for core commands +64 - 15,359 | *UNALLOCATED* +15,360 - 16,383 | Vendor-specific +16,384 - 1,999,999 | *UNALLOCATED* +2,000,000 - 2,097,151 | Experimental use only + +#### 3.3. Command Payload (Optional) #### + +Depending on the semantics of the command in question, a payload MAY +be included in the frame. The exact composition and length of the +payload is defined by the command identifier. + +## 4. Commands + +* CMD 0: (Host->NCP) `CMD_NOOP` +* CMD 1: (Host->NCP) `CMD_RESET` +* CMD 2: (Host->NCP) `CMD_PROP_VALUE_GET` +* CMD 3: (Host->NCP) `CMD_PROP_VALUE_SET` +* CMD 4: (Host->NCP) `CMD_PROP_VALUE_INSERT` +* CMD 5: (Host->NCP) `CMD_PROP_VALUE_REMOVE` +* CMD 6: (NCP->Host) `CMD_PROP_VALUE_IS` +* CMD 7: (NCP->Host) `CMD_PROP_VALUE_INSERTED` +* CMD 8: (NCP->Host) `CMD_PROP_VALUE_REMOVED` +* CMD 9: (Host->NCP) `CMD_NET_SAVE` (See section B.1.1.) +* CMD 10: (Host->NCP) `CMD_NET_CLEAR` (See section B.1.2.) +* CMD 11: (Host->NCP) `CMD_NET_RECALL` (See section B.1.3.) +* CMD 12: (NCP->Host) `CMD_HBO_OFFLOAD` (See section C.1.1.) +* CMD 13: (NCP->Host) `CMD_HBO_RECLAIM` (See section C.1.2.) +* CMD 14: (NCP->Host) `CMD_HBO_DROP` (See section C.1.3.) +* CMD 15: (Host->NCP) `CMD_HBO_OFFLOADED` (See section C.1.4.) +* CMD 16: (Host->NCP) `CMD_HBO_RECLAIMED` (See section C.1.5.) +* CMD 17: (Host->NCP) `CMD_HBO_DROPPED` (See section C.1.6.) + +### 4.1. CMD 0: (Host->NCP) `CMD_NOOP` + +Octets: | 1 | 1 +--------|--------|---------- +Fields: | HEADER | CMD_NOOP + +No-Operation command. Induces the NCP to send a success status back to +the host. This is primarily used for livliness checks. + +The command payload for this command SHOULD be empty. The receiver +MUST ignore any non-empty command payload. + +There is no error condition for this command. + + + +### 4.2. CMD 1: (Host->NCP) `CMD_RESET` + +Octets: | 1 | 1 +--------|--------|---------- +Fields: | HEADER | CMD_RESET + +Reset NCP command. Causes the NCP to perform a software reset. Due to +the nature of this command, the TID is ignored. The host should +instead wait for a `CMD_PROP_VALUE_IS` command from the NCP indicating +`PROP_LAST_STATUS` has been set to `STATUS_RESET_SOFTWARE`. + +The command payload for this command SHOULD be empty. The receiver +MUST ignore any non-empty command payload. + +If an error occurs, the value of `PROP_LAST_STATUS` will be emitted +instead with the value set to the generated status code for the error. + + + +### 4.3. CMD 2: (Host->NCP) `CMD_PROP_VALUE_GET` + +Octets: | 1 | 1 | 1-3 +--------|--------|--------------------|--------- +Fields: | HEADER | CMD_PROP_VALUE_GET | PROP_ID + +Get property value command. Causes the NCP to emit a +`CMD_PROP_VALUE_IS` command for the given property identifier. + +The payload for this command is the property identifier encoded in the +packed unsigned integer format described in section 7.2. + +If an error occurs, the value of `PROP_LAST_STATUS` will be emitted +instead with the value set to the generated status code for the error. + + + +### 4.4. CMD 3: (Host->NCP) `CMD_PROP_VALUE_SET` + +Octets: | 1 | 1 | 1-3 | *n* +--------|--------|--------------------|---------|------------ +Fields: | HEADER | CMD_PROP_VALUE_SET | PROP_ID | PROP_VALUE + +Set property value command. Instructs the NCP to set the given +property to the specific given value, replacing any previous value. + +The payload for this command is the property identifier encoded in the +packed unsigned integer format described in section 7.2, followed by +the property value. The exact format of the property value is defined +by the property. + +If an error occurs, the value of `PROP_LAST_STATUS` will be emitted +with the value set to the generated status code for the error. + + + +### 4.5. CMD 4: (Host->NCP) `CMD_PROP_VALUE_INSERT` + +Octets: | 1 | 1 | 1-3 | *n* +--------|--------|-----------------------|---------|------------ +Fields: | HEADER | CMD_PROP_VALUE_INSERT | PROP_ID | VALUE_TO_INSERT + +Insert value into property command. Instructs the NCP to insert the +given value into a list-oriented property, without removing other +items in the list. The resulting order of items in the list is defined +by the individual property being operated on. + +The payload for this command is the property identifier encoded in the +packed unsigned integer format described in section 7.2, followed by +the value to be inserted. The exact format of the value is defined by +the property. + +If an error occurs, the value of `PROP_LAST_STATUS` will be emitted +with the value set to the generated status code for the error. + + + +### 4.6. CMD 5: (Host->NCP) `CMD_PROP_VALUE_REMOVE` + +Octets: | 1 | 1 | 1-3 | *n* +--------|--------|-----------------------|---------|------------ +Fields: | HEADER | CMD_PROP_VALUE_REMOVE | PROP_ID | VALUE_TO_REMOVE + +Remove value from property command. Instructs the NCP to remove the +given value from a list-oriented property, without affecting other +items in the list. The resulting order of items in the list is defined +by the individual property being operated on. + +Note that this command operates *by value*, not by index! + +The payload for this command is the property identifier encoded in the +packed unsigned integer format described in section 7.2, followed by +the value to be inserted. The exact format of the value is defined by +the property. + +If an error occurs, the value of `PROP_LAST_STATUS` will be emitted +with the value set to the generated status code for the error. + + +### 4.7. CMD 6: (NCP->Host) `CMD_PROP_VALUE_IS` + +Octets: | 1 | 1 | 1-3 | *n* +--------|--------|-------------------|---------|------------ +Fields: | HEADER | CMD_PROP_VALUE_IS | PROP_ID | PROP_VALUE + +Property value notification command. This command can be sent by the +NCP in response to a previous command from the host, or it can be sent +by the NCP in an unsolicited fashion to notify the host of various +state changes asynchronously. + +The payload for this command is the property identifier encoded in the +packed unsigned integer format described in section 7.2, followed by +the current value of the given property. + + + +### 4.8. CMD 7: (NCP->Host) `CMD_PROP_VALUE_INSERTED` + +Octets: | 1 | 1 | 1-3 | *n* +--------|--------|-------------------------|---------|------------ +Fields: | HEADER | CMD_PROP_VALUE_INSERTED | PROP_ID | PROP_VALUE + +Property value insertion notification command. This command can be +sent by the NCP in response to the `CMD_PROP_VALUE_INSERT` command, or +it can be sent by the NCP in an unsolicited fashion to notify the host +of various state changes asynchronously. + +The payload for this command is the property identifier encoded in the +packed unsigned integer format described in section 7.2, followed by +the value that was inserted into the given property. + +The resulting order of items in the list is defined by the given +property. + +### 4.9. CMD 8: (NCP->Host) `CMD_PROP_VALUE_REMOVED` + +Octets: | 1 | 1 | 1-3 | *n* +--------|--------|------------------------|---------|------------ +Fields: | HEADER | CMD_PROP_VALUE_REMOVED | PROP_ID | PROP_VALUE + +Property value removal notification command. This command can be sent +by the NCP in response to the `CMD_PROP_VALUE_REMOVE` command, or it +can be sent by the NCP in an unsolicited fashion to notify the host of +various state changes asynchronously. + +Note that this command operates *by value*, not by index! + +The payload for this command is the property identifier encoded in the +packed unsigned integer format described in section 7.2, followed by +the value that was removed from the given property. + +The resulting order of items in the list is defined by the given +property. + + + + + + +## 5. General Properties + +While the majority of the properties that allow the configuration +of network connectivity are network protocol specific, there are +several properties that are required in all implementations. + +* 0: `PROP_LAST_STATUS` +* 1: `PROP_PROTOCOL_VERSION` +* 2: `PROP_CAPABILITIES` +* 3: `PROP_NCP_VERSION` +* 4: `PROP_INTERFACE_COUNT` +* 5: `PROP_POWER_STATE` +* 6: `PROP_HWADDR` +* 7: `PROP_LOCK` +* 8: `PROP_HBO_MEM_MAX` (See section C.2.1.) +* 9: `PROP_HBO_BLOCK_MAX` (See section C.2.2.) +* 112: `PROP_STREAM_DEBUG` +* 113: `PROP_STREAM_RAW` +* 114: `PROP_STREAM_NET` +* 115: `PROP_STREAM_NET_INSECURE` + + + + +### 5.1. PROP 0: `PROP_LAST_STATUS` + +* Type: Read-Only +* Encoding: `i` + +Octets: | 1-3 +--------|------------------ +Fields: | PROP_LAST_STATUS + +Describes the status of the last operation. Encoded as a packed +unsigned integer. + +This property is emitted often to indicate the result status of +pretty much any Host-to-NCP operation. + +It is emitted automatically at NCP startup with a value indicating +the reset reason. + +See section 6 for the complete list of status codes. + +### 5.2. PROP 1: `PROP_PROTOCOL_VERSION` + +* Type: Read-Only +* Encoding: `iiii` + +Octets: | 1-3 | 1-3 | 1-3 | 1-3 +--------|----------------|---------------|---------------|------------------- +Fields: | INTERFACE_TYPE | MAJOR_VERSION | MINOR_VERSION | VENDOR_IDENTIFIER + +Describes the protocol version information. This property contains +four fields, each encoded as a packed unsigned integer: + + * Interface Type + * Major Version Number + * Minor Version Number + * Vendor Identifier + +#### 5.2.1 Interface Type #### + +This integer identifies what the network protocol for this NCP. +Currently defined values are: + + * 1: ZigBee + * 2: ZigBeeIP + * 3: Thread + * TBD: BlueTooth Low Energy (BTLE) + +The host MUST enter a FAULT state if it does not recognize the +protocol given by the NCP. + +#### 5.2.2 Major Version Number #### + +The major version number is used to identify large and incompatible +differences between protocol versions. + +The host MUST enter a FAULT state if it does not explicitly support +the given major version number. + +#### 5.2.3 Minor Version Number #### + +The minor version number is used to identify small but otherwise +compatible differences between protocol versions. A mismatch between +the advertised minor version number and the minor version that is +supported by the host SHOULD NOT be fatal to the operation of the +host. + +#### 5.2.3 Vendor Identifier #### + + +### 5.3. PROP 2: `PROP_CAPABILITIES` + +* Type: Read-Only +* Packed-Encoding: `A(i)` + +Octets: | 1-3 | 1-3 | ... +--------|-------|-------|----- +Fields: | CAP_1 | CAP_2 | ... + +Describes the supported capabilities of this NCP. Encoded as a list of +packed unsigned integers. + +A capability is defined as a 21-bit integer that describes a subset of +functionality which is supported by the NCP. + +Currently defined values are: + + * 1: `CAP_LOCK` + * 2: `CAP_NET_SAVE` + * 3: `CAP_HBO`: Host Block Offload (See Section C.) + * 4: `CAP_POWER_SAVE` + * 16: `CAP_802_15_4_2003` + * 17: `CAP_802_15_4_2006` + * 18: `CAP_802_15_4_2011` + * 21: `CAP_802_15_4_PIB` + * 24: `CAP_802_15_4_2450MHZ_OQPSK` + * 25: `CAP_802_15_4_915MHZ_OQPSK` + * 26: `CAP_802_15_4_868MHZ_OQPSK` + * 27: `CAP_802_15_4_915MHZ_BPSK` + * 28: `CAP_802_15_4_868MHZ_BPSK` + * 29: `CAP_802_15_4_915MHZ_ASK` + * 30: `CAP_802_15_4_868MHZ_ASK` + * 48: `CAP_ROLE_ROUTER` + * 49: `CAP_ROLE_SLEEPY` + * 52: `CAP_NET_THREAD_1_0` + +Additionally, future capability allocations SHALL be made from the +following allocation plan: + +Capability Range | Description +----------------------|------------------ +0 - 127 | Reserved for core capabilities +64 - 15,359 | *UNALLOCATED* +15,360 - 16,383 | Vendor-specific +16,384 - 1,999,999 | *UNALLOCATED* +2,000,000 - 2,097,151 | Experimental use only + +### 5.4. PROP 3: `PROP_NCP_VERSION` + +* Type: Read-Only +* Packed-Encoding: `U` + +Octets: | *n* +--------|------------------- +Fields: | `NCP_VESION_STRING` + +Contains a string which describes the firmware currently running on +the NCP. Encoded as a zero-terminated UTF-8 string. + +The format of the string is not strictly defined, but it is intended +to present similarly to the "User-Agent" string from HTTP. The +RECOMMENDED format of the string is as follows: + + /[][; ]; + +Examples: + + * `OpenThread/1.0d26-25-gb684c7f; DEBUG; May 9 2016 18:22:04` + * `ConnectIP/2.0b125 s1 ALPHA; Sept 24 2015 20:49:19` + +### 5.5. PROP 4: `PROP_INTERFACE_COUNT` + +* Type: Read-Only +* Packed-Encoding: `C` + +Octets: | 1 +--------|----------------- +Fields: | `INTERFACE_COUNT` + +Describes the number of concurrent interfaces supported by this NCP. +Since the concurrent interface mechanism is still TBD, this value MUST +always be one. + +This value is encoded as an unsigned 8-bit integer. + +### 5.6. PROP 5: `PROP_POWER_STATE` + +* Type: Read-Write +* Packed-Encoding: `C` + +Octets: | 1 +--------|------------------ +Fields: | `PROP_POWER_STATE` + +Describes the current power state of the NCP. By writing to this +property you can manage the lower state of the NCP. Enumeration is +encoded as a single unsigned byte. + +Defined values are: + + * 0: `POWER_STATE_OFFLINE`: NCP is physically powered off. + (Enumerated for completeness sake, not expected on the wire) + * 1: `POWER_STATE_DEEP_SLEEP`: Almost everything on the NCP is shut + down, but can still be resumed via a command or interrupt. + * 2: `POWER_STATE_STANDBY`: NCP is in the lowest power state that + can still be awoken by an event from the radio (e.g. waiting for + alarm) + * 3: `POWER_STATE_LOW_POWER`: NCP is responsive (and possibly + connected), but using less power. (e.g. "Sleepy" child node) + * 4: `POWER_STATE_ONLINE`: NCP is fully powered. (e.g. "Parent" + node) + +### 5.7. PROP 6: `PROP_HWADDR` + +* Type: Read-Only* +* Packed-Encoding: `E` + +Octets: | 8 +--------|------------ +Fields: | PROP_HWADDR + +The static EUI64 address of the device. This value is read-only, but +may be writable under certain vendor-defined circumstances. + +### 5.8. PROP 6: `PROP_LOCK` + +* Type: Read-Write +* Packed-Encoding: `b` + +Octets: | 1 +--------|------------ +Fields: | PROP_LOCK + +Property lock. Used for grouping changes to several properties to +take effect at once, or to temporarily prevent the automatic updating +of property values. When this property is set, the execution of the +NCP is effectively frozen until it is cleared. + +This property is only supported if the `CAP_LOCK` capability is present. + +### 5.9. PROP 112: `PROP_STREAM_DEBUG` + +* Type: Read-Only-Stream +* Packed-Encoding: `U` + +Octets: | *n* +--------|------------ +Fields: | UTF8_DATA + +This property is a streaming property, meaning that you cannot explicitly +fetch the value of this property. The stream provides human-readable debugging +output which may be displayed in the host logs. + +To receive the debugging stream, you wait for `CMD_PROP_VALUE_IS` commands for +this property from the NCP. + +### 5.10. PROP 113: `PROP_STREAM_RAW` + +* Type: Read-Write-Stream +* Packed-Encoding: `DD` + +Octets: | 2 | *n* | *n* +--------|----------------|------------|---------------- +Fields: | FRAME_DATA_LEN | FRAME_DATA | FRAME_METADATA + +This stream provides the capability of sending and receiving raw packets +to and from the radio. The exact format of the frame metadata and data is +dependent on the MAC and PHY being used. + +This property is a streaming property, meaning that you cannot explicitly +fetch the value of this property. To receive traffic, you wait for +`CMD_PROP_VALUE_IS` commands with this property id from the NCP. + +Implementations may optionally support the ability to transmit arbitrary +raw packets. If this capability is supported, you may call `CMD_PROP_VALUE_SET` +on this property with the value of the raw packet. + +Any data past the end of `FRAME_DATA_LEN` is considered metadata. The format +of the metadata is defined by the associated MAC and PHY being used. + +### 5.11. PROP 114: `PROP_STREAM_NET` + +* Type: Read-Write-Stream +* Packed-Encoding: `DD` + +Octets: | 2 | *n* | *n* +--------|----------------|------------|---------------- +Fields: | FRAME_DATA_LEN | FRAME_DATA | FRAME_METADATA + +This stream provides the capability of sending and receiving data packets +to and from the currently attached network. The exact format of the frame +metadata and data is dependent on the network protocol being used. + +This property is a streaming property, meaning that you cannot explicitly +fetch the value of this property. To receive traffic, you wait for +`CMD_PROP_VALUE_IS` commands with this property id from the NCP. + +To send network packets, you call `CMD_PROP_VALUE_SET` on this property with +the value of the packet. + +Any data past the end of `FRAME_DATA_LEN` is considered metadata. The format +of the metadata is defined by the associated network protocol. + +### 5.12. PROP 114: `PROP_STREAM_NET_INSECURE` + +* Type: Read-Write-Stream +* Packed-Encoding: `DD` + +Octets: | 2 | *n* | *n* +--------|----------------|------------|---------------- +Fields: | FRAME_DATA_LEN | FRAME_DATA | FRAME_METADATA + +This stream provides the capability of sending and receiving unencrypted +and unauthenticated data packets to and from nearby devices for the +purposes of device commissioning. The exact format of the frame +metadata and data is dependent on the network protocol being used. + +This property is a streaming property, meaning that you cannot explicitly +fetch the value of this property. To receive traffic, you wait for +`CMD_PROP_VALUE_IS` commands with this property id from the NCP. + +To send network packets, you call `CMD_PROP_VALUE_SET` on this property with +the value of the packet. + +Any data past the end of `FRAME_DATA_LEN` is considered metadata. The format +of the metadata is defined by the associated network protocol. + + +## 6. Status Codes + + * 0: `STATUS_OK`: Operation has completed successfully. + * 1: `STATUS_FAILURE`: Operation has failed for some undefined + reason. + * 2: `STATUS_UNIMPLEMENTED` + * 3: `STATUS_INVALID_ARGUMENT` + * 4: `STATUS_INVALID_STATE` + * 5: `STATUS_INVALID_COMMAND` + * 6: `STATUS_INVALID_INTERFACE` + * 7: `STATUS_INTERNAL_ERROR` + * 8: `STATUS_SECURITY_ERROR` + * 9: `STATUS_PARSE_ERROR` + * 10: `STATUS_IN_PROGRESS` + * 11: `STATUS_NOMEM` + * 12: `STATUS_BUSY` + * 13: `STATUS_PROPERTY_NOT_FOUND` + * 14: `STATUS_PACKET_DROPPED` + * 15: `STATUS_EMPTY` + * 16-111: *RESERVED* + * 112-127: Reset Causes + * 112: `STATUS_RESET_POWER_ON` + * 113: `STATUS_RESET_EXTERNAL` + * 114: `STATUS_RESET_SOFTWARE` + * 115: `STATUS_RESET_FAULT` + * 116: `STATUS_RESET_CRASH` + * 117: `STATUS_RESET_ASSERT` + * 118: `STATUS_RESET_OTHER` + * 119-127: *RESERVED-RESET-CODES* + * 128 - 15,359: *UNALLOCATED* + * 15,360 - 16,383: Vendor-specific + * 16,384 - 1,999,999: *UNALLOCATED* + * 2,000,000 - 2,097,151: Experimental Use Only (MUST NEVER be used + in production!) + +## 7. Data Packing + +Data serialization for properties is performed using a light-weight +data packing format which was loosely inspired by D-Bus. The format of +a serialization is defined by a specially formatted string. + +Goals: + + * Be very lightweight and favor direct representation of values. + * Use an easily readable and memorable format string. + * Support lists and structures. + * Allow properties to be appended to structures while maintaining + backward compatibility. + +Each primitive datatype has an ASCII character associated with it. +Structures can be represented as strings of these characters. For +example: + + * `"C"`: A single unsigned byte. + * `"C6U"`: A single unsigned byte, followed by a 128-bit IPv6 + address, followed by a zero-terminated UTF8 string. + * `"A(6)"`: An array of IPv6 addresses + +In each case, the data is represented exactly as described. For +example, an array of 10 IPv6 address is stored as 160 bytes. + +### 7.1 Primitive Types + + * 0: `DATATYPE_NULL` + * `'.'`: `DATATYPE_VOID`: Empty data type. Used internally. + * `'b'`: `DATATYPE_BOOL`: Boolean value. Encoded in 8-bits as either + `0x00` or `0x01`. All other values are illegal. + * `'C'`: `DATATYPE_UINT8`: Unsigned 8-bit integer. + * `'c'`: `DATATYPE_INT8`: Signed 8-bit integer. + * `'S'`: `DATATYPE_UINT16`: Unsigned 16-bit integer. (Little-endian) + * `'s'`: `DATATYPE_INT16`: Signed 16-bit integer. (Little-endian) + * `'L'`: `DATATYPE_UINT32`: Unsigned 32-bit integer. (Little-endian) + * `'l'`: `DATATYPE_INT32`: Signed 32-bit integer. (Little-endian) + * `'i'`: `DATATYPE_UINT_PACKED`: Packed Unsigned Integer. (See + section 7.2) + * `'6'`: `DATATYPE_IPv6ADDR`: IPv6 Address. (Big-endian) + * `'E'`: `DATATYPE_EUI64`: EUI-64 Address. (Big-endian) + * `'e'`: `DATATYPE_EUI48`: EUI-48 Address. (Big-endian) + * `'D'`: `DATATYPE_DATA`: Arbitrary Data. (See section 7.3) + * `'U'`: `DATATYPE_UTF8`: Zero-terminated UTF8-encoded string. + * `'T'`: `DATATYPE_STRUCT`: Structured datatype. Compound type. (See + section 7.4) + * `'A'`: `DATATYPE_ARRAY`: Array of datatypes. Compound type. (See + section 7.5) + +### 7.2 Packed Unsigned Integer + +For certain types of integers, such command or property identifiers, +usually have a value on the wire that is less than 127. However, in +order to not preclude the use of values larger than 256, we would need +to add an extra byte. Doing this would add an extra byte to the vast +majority of instances, which can add up in terms of bandwidth. + +The packed unsigned integer format is inspired by the encoding scheme +in UTF8 identical to the unsigned integer format in EXI. + +For all values less than 127, the packed form of the number is simply +a single byte which directly represents the number. For values larger +than 127, the following process is used to encode the value: + +1. The unsigned integer is broken up into *n* 7-bit chunks and placed + into *n* octets, leaving the most significant bit of each octet + unused. +2. Order the octets from least-significant to most-significant. + (Little-endian) +3. Clear the most significant bit of the most significant octet. Set + the least significant bit on all other octets. + +Where *n* is the smallest number of 7-bit chunks you can use to +represent the given value. + +Take the value 1337, for example: + + 1337 => 0x0539 + => [39 0A] + => [B9 0A] + +To decode the value, you collect the 7-bit chunks until you find an +octet with the most significant bit clear. + +### 7.3 Data Blobs + +Data blobs are special datatypes in that the data that they contain +does not inherently define the size of the data. This means that if +the length of the data blob isn't *implied*, then the length of the +blob must be prepended as a packed unsigned integer. + +The length of a data blob is *implied* only when it is the last +datatype in a given buffer. This works because we already know the +size of the buffer, and the length of the data is simply the rest of +the size of the buffer. + +For example, let's say we have a buffer that is encoded with the +datatype signature of `CLLD`. In this case, it is pretty easy to tell +where the start and end of the data blob is: the start is 9 bytes from +the start of the buffer, and its length is the length of the buffer +minus 9. (9 is the number of bytes taken up by a byte and two longs) + +However, things are a little different with `CLDL`. Since our datablob +is no longer the last item in the signature, the length must be +prepended. + +If you are a little confused, keep reading. This theme comes up in a a +few different ways in the following sections. + +When a length is prepended, the length is encoded as a little-endian +unsigned 16-bit integer. + +### 7.4 Structured Data + +The structured data type is a way of bundling together a bunch of data +into a single data structure. This may at first seem useless. What is +the difference between `T(Cii)` and just `Cii`? The answer is, in that +particular case, nothing: they are stored in exactly the same way. + +However, one case where the structure datatype makes a difference is +when you compare `T(Cii)L` to `CiiL`: they end up being represented +entirely differently. This is because the structured data type follows +the exact same semantics as the data blob type: if it isn't the last +datatype in a signature, *it must be prepended with a length*. This is +useful because it allows for new datatypes to be appended to the +structure's signature while remaining *backward parsing +compatibility*. + +More explicitly, if you take data that was encoded with `T(Cii6)L`, +you can still decode it as `T(Cii)L`. + +Let's take, for example, the property `PROP_IPv6_ADDR_TABLE`. +Conceptually it is just a list of IPv6 addresses, so we can encode it +as `A(6c)`. However, if we ever want to associate more data with the +type (like flags), we break our backward compatibility if we add +another member and use `A(6cC)`. To allow for data to be added without +breaking backward compatibility, we use the structured data type from +the start: `A(T(6c))`. Then when we add a new member to the structure +(`A(T(6cC))`), we don't break backward compatibility. + +It's also worth noting that `T(Cii)L` also parses as `DL`. You could +then take the resultant data blob and parse it as `Cii`. + +When a length is prepended, the length is encoded as a little-endian +unsigned 16-bit integer. + +### 7.5 Arrays + +An array is simply a concatenated set of *n* data encodings. For example, +the type `A(6)` is simply a list of IPv6 addresses---one after the other. + +Just like the data blob type and the structured data type, the length +of the entire array must be prepended *unless* the array is the last +type in a given signature. Thus, `A(C)` (An array of unsigned bytes) +encodes identically to `D`. + +When a length is prepended, the length is encoded as a little-endian +unsigned 16-bit integer. + +## A. Framing Protocol + +Since this NCP protocol is defined independently of framing, any +number of framing protocols could be used successfully. However, +in the interests of cross-compatibility, we recommend using +HDLC-Lite for framing when using this NCP protocol with a UART. + +A SPI-specific framing mechanism is currently TBD. + +## B. Feature: Network Save + +The network save feature is an optional NCP capability that, when +present, allows the host to save and recall network credentials and +state to and from nonvolatile storage. + +The presence of this feature can be detected by checking for the +presence of the `CAP_NET_SAVE` capability in `PROP_CAPABILITIES`. + +### B.1. Commands + +#### B.1.1. CMD 9: (Host->NCP) `CMD_NET_SAVE` + +Octets: | 1 | 1 +--------|--------|-------------- +Fields: | HEADER | CMD_NET_SAVE + +Save network state command. Saves any current network credentials and +state necessary to reconnect to the current network to non-volatile +memory. + +This operation affects non-volatile memory only. The current network +information stored in volatile memory is unaffected. + +The response to this command is always a `CMD_PROP_VALUE_IS` for +`PROP_LAST_STATUS`, indicating the result of the operation. + +This command is only available if the `CAP_NET_SAVE` capability is +set. + + + +#### B.1.2. CMD 10: (Host->NCP) `CMD_NET_CLEAR` + +Octets: | 1 | 1 +--------|--------|--------------- +Fields: | HEADER | CMD_NET_CLEAR + +Clear saved network state command. Clears any previously saved network +credentials and state previously stored by `CMD_NET_SAVE` from +non-volatile memory. + +This operation affects non-volatile memory only. The current network +information stored in volatile memory is unaffected. + +The response to this command is always a `CMD_PROP_VALUE_IS` for +`PROP_LAST_STATUS`, indicating the result of the operation. + +This command is only available if the `CAP_NET_SAVE` capability is +set. + + + +#### B.1.3. CMD 11: (Host->NCP) `CMD_NET_RECALL` + +Octets: | 1 | 1 +--------|--------|---------------- +Fields: | HEADER | CMD_NET_RECALL + +Recall saved network state command. Recalls any previously saved +network credentials and state previously stored by `CMD_NET_SAVE` from +non-volatile memory. + +This command will typically generated several unsolicited property +updates as the network state is loaded. At the conclusion of loading, +the authoritative response to this command is always a +`CMD_PROP_VALUE_IS` for `PROP_LAST_STATUS`, indicating the result of +the operation. + +This command is only available if the `CAP_NET_SAVE` capability is +set. + + +## C. Feature: Host Buffer Offload + +The memory on an NCP may be much more limited than the memory on +the host processor. In such situations, it is sometimes useful +for the NCP to offload buffers to the host processor temporarily +so that it can perform other operations. + +Host buffer offload is an optional NCP capability that, when +present, allows the NCP to store data buffers on the host processor +that can be recalled at a later time. + +The presence of this feature can be detected by the host by +checking for the presence of the `CAP_HBO` +capability in `PROP_CAPABILITIES`. + +### C.1. Commands + +#### C.1.1. CMD 12: (NCP->Host) `CMD_HBO_OFFLOAD` + +* Argument-Encoding: `LscD` + * `OffloadId`: 32-bit unique block identifier + * `Expiration`: In seconds-from-now + * `Priority`: Critical, High, Medium, Low + * `Data`: Data to offload + +#### C.1.2. CMD 13: (NCP->Host) `CMD_HBO_RECLAIM` + * Argument-Encoding: `Lb` + * `OffloadId`: 32-bit unique block identifier + * `KeepAfterReclaim`: If not set to true, the block will be + dropped by the host after it is sent to the NCP. + +#### C.1.3. CMD 14: (NCP->Host) `CMD_HBO_DROP` + +* Argument-Encoding: `L` + * `OffloadId`: 32-bit unique block identifier + +#### C.1.1. CMD 15: (Host->NCP) `CMD_HBO_OFFLOADED` + +* Argument-Encoding: `Li` + * `OffloadId`: 32-bit unique block identifier + * `Status`: Status code for the result of the operation. + +#### C.1.2. CMD 16: (Host->NCP) `CMD_HBO_RECLAIMED` + +* Argument-Encoding: `LiD` + * `OffloadId`: 32-bit unique block identifier + * `Status`: Status code for the result of the operation. + * `Data`: Data that was previously offloaded (if any) + +#### C.1.3. CMD 17: (Host->NCP) `CMD_HBO_DROPPED` + +* Argument-Encoding: `Li` + * `OffloadId`: 32-bit unique block identifier + * `Status`: Status code for the result of the operation. + +### C.2. Properties + +#### C.2.1. PROP 6: `PROP_HBO_MEM_MAX` + +* Type: Read-Write +* Packed-Encoding: `L` + +Octets: | 4 +--------|----------------- +Fields: | `PROP_HBO_MEM_MAX` + +Describes the number of bytes that may be offloaded from the NCP to +the host. Default value is zero, so this property must be set by the +host to a non-zero value before the NCP will begin offloading blocks. + +This value is encoded as an unsigned 32-bit integer. + +This property is only available if the `CAP_HBO` +capability is present in `PROP_CAPABILITIES`. + +#### C.2.1. PROP 7: `PROP_HBO_BLOCK_MAX` + +* Type: Read-Write +* Packed-Encoding: `S` + +Octets: | 2 +--------|----------------- +Fields: | `PROP_HBO_BLOCK_MAX` + +Describes the number of blocks that may be offloaded from the NCP to +the host. Default value is 32. Setting this value to zero will cause +host block offload to be effectively disabled. + +This value is encoded as an unsigned 16-bit integer. + +This property is only available if the `CAP_HBO` +capability is present in `PROP_CAPABILITIES`. + + + + +## D. Protocol: Thread + +This section describes all of the properties and semantics required +for managing a thread NCP. + +### D.1. PHY Properties +#### D.1.1. PROP x: `PROP_PHY_ENABLED` +* Type: Read-Only +* Packed-Encoding: `b` + +#### D.1.4. PROP x: `PROP_PHY_CHAN` +* Type: Read-Write +* Packed-Encoding: `C` + +#### D.1.5. PROP x: `PROP_PHY_CHAN_SUPPORTED` +* Type: Read-Only +* Packed-Encoding: `A(C)` +* Unit: List of channels + +#### D.1.3. PROP x: `PROP_PHY_FREQ` +* Type: Read-Only +* Packed-Encoding: `L` +* Unit: Kilohertz + +#### D.1.6. PROP x: `PROP_PHY_CCA_THRESHOLD` +* Type: Read-Write +* Packed-Encoding: `c` +* Unit: dBm + +#### D.1.7. PROP x: `PROP_PHY_TX_POWER` +* Type: Read-Write +* Packed-Encoding: `c` +* Unit: dBm + +#### D.1.6. PROP x: `PROP_PHY_RSSI` +* Type: Read-Only +* Packed-Encoding: `c` +* Unit: dBm + +#### D.1.7. PROP x: `PROP_PHY_RAW_STREAM_ENABLED` +* Type: Read-Write +* Packed-Encoding: `b` + +Set to true to enable raw frames to be emitted from `PROP_STREAM_RAW`. + + + +### D.2. MAC Properties + + +#### D.2.1. PROP x: `PROP_MAC_SCAN_STATE` +* Type: Read-Write +* Packed-Encoding: `C` +* Unit: Enumeration + +Possible Values: + +* 0: `SCAN_STATE_IDLE` +* 1: `SCAN_STATE_BEACON` +* 2: `SCAN_STATE_ENERGY` + +Set to `SCAN_STATE_BEACON` to start an active scan. +Beacons will be emitted from `PROP_MAC_SCAN_BEACON`. + +Set to `SCAN_STATE_ENERGY` to start an energy scan. +Channel energy will be reported by alternating emissions +of `PROP_PHY_CHAN` and `PROP_PHY_RSSI`. + +Values switches to `SCAN_STATE_IDLE` when scan is complete. + +#### D.2.2. PROP x: `PROP_MAC_SCAN_MASK` +* Type: Read-Write +* Packed-Encoding: `A(C)` +* Unit: List of channels to scan + +#### D.2.3. PROP x: `PROP_MAC_SCAN_BEACON` +* Type: Read-Only-Stream +* Packed-Encoding: `CcT(ESSC)T(i).` + +chan,rssi,(laddr,saddr,panid,lqi),(proto,xtra) + +chan,rssi,(laddr,saddr,panid,lqi),(proto,flags,networkid,xpanid) [CcT(ESSC)T(iCUD.).] + +#### D.2.4. PROP x: `PROP_MAC_15_4_LADDR` +* Type: Read-Write +* Packed-Encoding: `E` + +#### D.2.5. PROP x: `PROP_MAC_15_4_SADDR` +* Type: Read-Write +* Packed-Encoding: `S` + +#### D.2.6. PROP x: `PROP_MAC_15_4_PANID` +* Type: Read-Write +* Packed-Encoding: `S` + + + + + +### D.3. NET Properties + +#### D.3.1. PROP x: `PROP_NET_SAVED` +* Type: Read-Only +* Packed-Encoding: `b` + +#### D.3.2. PROP x: `PROP_NET_ENABLED` +* Type: Read-Only +* Packed-Encoding: `b` + +#### D.3.3. PROP x: `PROP_NET_STATE` +* Type: Read-Write +* Packed-Encoding: `C` +* Unit: Enumeration + +Values: + +* 0: `NET_STATE_OFFLINE` +* 1: `NET_STATE_DETACHED` +* 2: `NET_STATE_ATTACHING` +* 3: `NET_STATE_ATTACHED` + +#### D.3.4. PROP x: `PROP_NET_ROLE` +* Type: Read-Write +* Packed-Encoding: `C` +* Unit: Enumeration + +Values: + +* 0: `NET_ROLE_NONE` +* 1: `NET_ROLE_CHILD` +* 2: `NET_ROLE_ROUTER` +* 3: `NET_ROLE_LEADER` + +#### D.3.5. PROP x: `PROP_NET_NETWORK_NAME` +* Type: Read-Write +* Packed-Encoding: `U` + +#### D.3.6. PROP x: `PROP_NET_XPANID` +* Type: Read-Write +* Packed-Encoding: `D` + +#### D.3.7. PROP x: `PROP_NET_MASTER_KEY` +* Type: Read-Write +* Packed-Encoding: `D` + +#### D.3.8. PROP x: `PROP_NET_KEY_SEQUENCE` +* Type: Read-Write +* Packed-Encoding: `L` + +#### D.3.9. PROP x: `PROP_NET_PARTITION_ID` +* Type: Read-Write +* Packed-Encoding: `L` + + + +#### D.4. THREAD Properties + + +#### D.4.1. PROP x: `PROP_THREAD_LEADER` +* Type: Read-Write +* Packed-Encoding: `6` + +#### D.4.2. PROP x: `PROP_THREAD_PARENT` +* Type: Read-Write +* Packed-Encoding: `ES` +* LADDR, SADDR + +#### D.4.3. PROP x: `PROP_THREAD_CHILD_TABLE` +* Type: Read-Write +* Packed-Encoding: `A(T(ES))` +* LADDR, SADDR + + + +### D.5. IPv6 Properties + +#### D.5.1. PROP x: `PROP_IPV6_LL_ADDR` +* Type: Read-Only +* Packed-Encoding: `6` + +IPv6 Address + +#### D.5.2. PROP x: `PROP_IPV6_ML_ADDR` +* Type: Read-Only +* Packed-Encoding: `6` + +IPv6 Address + Prefix Length + +#### D.5.2. PROP x: `PROP_IPV6_ML_PREFIX` +* Type: Read-Write +* Packed-Encoding: `6C` + +IPv6 Prefix + Prefix Length + +#### D.5.3. PROP x: `PROP_IPV6_ADDRESS_TABLE` + +* Type: Read-Write +* Packed-Encoding: `A(T(6CLLC))` + +Array of structures containing: + +* `6`: IPv6 Address +* `C`: Network Prefix Length +* `L`: Valid Lifetime +* `L`: Preferred Lifetime +* `C`: Flags + +#### D.4.3. PROP x: `PROP_IPv6_ROUTE_TABLE` +* Type: Read-Write +* Packed-Encoding: `A(T(6C6))` + +Array of structures containing: + +* `6`: IPv6 Address +* `C`: Network Prefix Length +* `6`: Next Hop diff --git a/src/ncp/flen.cpp b/src/ncp/flen.cpp new file mode 100644 index 000000000..1072195eb --- /dev/null +++ b/src/ncp/flen.cpp @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2016, Nest Labs, Inc. + * 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. + */ + +/** + * @file + * This file implements an FLEN encoder and decoder. + */ + +#include +#include + +namespace Thread { +namespace Flen { + +enum +{ + kFlagSequence = 0x7e, ///< FLen Flag value +}; + +ThreadError Encoder::Init(uint8_t *aOutBuf, uint16_t &aOutLength) +{ + ThreadError error = kThreadError_None; + + mOutBuf = aOutBuf; + + VerifyOrExit(aOutLength >= 3, error = kThreadError_NoBufs); + aOutBuf[0] = kFlagSequence; + // Leave the next two bytes empty. + aOutLength = 3; + mOutLength = 0; + +exit: + return error; +} + +ThreadError Encoder::Encode(uint8_t aInByte, uint8_t *aOutBuf, uint16_t aOutLength) +{ + ThreadError error = kThreadError_None; + + VerifyOrExit(mOutOffset + 1 < aOutLength, error = kThreadError_NoBufs); + aOutBuf[mOutOffset++] = aInByte; + +exit: + return error; +} + +ThreadError Encoder::Encode(const uint8_t *aInBuf, uint16_t aInLength, uint8_t *aOutBuf, uint16_t &aOutLength) +{ + ThreadError error = kThreadError_None; + + mOutOffset = 0; + + for (int i = 0; i < aInLength; i++) + { + SuccessOrExit(error = Encode(aInBuf[i], aOutBuf, aOutLength)); + } + +exit: + aOutLength = mOutOffset; + mOutLength += aOutLength; + return error; +} + +ThreadError Encoder::Finalize(uint8_t *aOutBuf, uint16_t &aOutLength) +{ + ThreadError error = kThreadError_None; + + VerifyOrExit(mOutOffset < aOutLength, error = kThreadError_NoBufs); + + mOutBuf[1] = (mOutLength >> 8); + mOutBuf[2] = (mOutLength & 0xFF); + + aOutLength = 0; + +exit: + return error; +} + + +Decoder::Decoder(uint8_t *aOutBuf, uint16_t aOutLength, FrameHandler aFrameHandler, void *aContext) +{ + mState = kStateNeedFlag; + mFrameHandler = aFrameHandler; + mContext = aContext; + mOutBuf = aOutBuf; + mOutOffset = 0; + mOutLength = aOutLength; +} + +void Decoder::Decode(const uint8_t *aInBuf, uint16_t aInLength) +{ + uint8_t byte; + + for (int i = 0; i < aInLength; i++) + { + byte = aInBuf[i]; + + switch (mState) + { + case kStateNeedFlag: + if (byte == kFlagSequence) + { + mState = kStateNeedLenH; + mOutOffset = 0; + } + + break; + + case kStateNeedLenH: + mReadLength = (byte << 8); + mState = kStateNeedLenL; + break; + + case kStateNeedLenL: + mReadLength += byte; + + if (mReadLength > mOutLength) + { + // Too big. + mState = kStateNeedFlag; + } + else + { + mState = kStateNeedData; + } + + break; + + case kStateNeedData: + mOutBuf[mOutOffset++] = byte; + + if (mOutOffset >= mReadLength) + { + mState = kStateNeedFlag; + mFrameHandler(mContext, mOutBuf, mReadLength); + } + + break; + } + } +} + +} // namespace Flen +} // namespace Thread diff --git a/src/ncp/flen.hpp b/src/ncp/flen.hpp new file mode 100644 index 000000000..ad485c50b --- /dev/null +++ b/src/ncp/flen.hpp @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2016, Nest Labs, Inc. + * 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. + */ + +/** + * @file + * This file includes definitions for an FLEN (flag/length) encoder and decoder. + */ + +#ifndef FLEN_HPP_ +#define FLEN_HPP_ + +#include +#include + +namespace Thread { + +/** + * @namespace Thread::Flen + * + * @brief + * This namespace includes definitions for the FLEN encoder and decoder. + * + */ +namespace Flen { + +/** + * This class implements the FLEN encoder. + * + */ +class Encoder +{ +public: + /** + * This method begins an FLEN frame and puts the initial bytes into @p aOutBuf. + * + * @param[in] aOutBuf A pointer to the output buffer. + * @param[inout] aOutLength On entry, the output buffer size; On exit, the output length. + * + * @retval kThreadError_None Successfully started the FLEN frame. + * @retval kThreadError_NoBufs Insufficient buffer space available to start the FLEN frame. + * + */ + ThreadError Init(uint8_t *aOutBuf, uint16_t &aOutLength); + + /** + * This method encodes the frame. + * + * @param[in] aInBuf A pointer to the input buffer. + * @param[in] aInLength The number of bytes in @p aInBuf to encode. + * @param[out] aOutBuf A pointer to the output buffer. + * @param[out] aOutLength On exit, the number of bytes placed in @p aOutBuf. + * + * @retval kThreadError_None Successfully encoded the FLEN frame. + * @retval kThreadError_NoBufs Insufficient buffer space available to encode the FLEN frame. + * + */ + ThreadError Encode(const uint8_t *aInBuf, uint16_t aInLength, uint8_t *aOutBuf, uint16_t &aOutLength); + + /** + * This method ends an FLEN frame and puts the initial bytes into @p aOutBuf. + * + * @param[in] aOutBuf A pointer to the output buffer. + * @param[inout] aOutLength On entry, the output buffer size; On exit, the output length. + * + * @retval kThreadError_None Successfully ended the FLEN frame. + * @retval kThreadError_NoBufs Insufficient buffer space available to end the FLEN frame. + * + */ + ThreadError Finalize(uint8_t *aOutBuf, uint16_t &aOutLength); + +private: + ThreadError Encode(uint8_t aInByte, uint8_t *aOutBuf, uint16_t aOutLength); + + uint8_t *mOutBuf; + uint16_t mOutOffset; + uint16_t mOutLength; +}; + +/** + * This class implements the FLEN decoder. + * + */ +class Decoder +{ +public: + /** + * This function pointer is called when a complete frame has been formed. + * + * @param[in] aContext A pointer to arbitrary context information. + * @param[in] aFrame A pointer to the frame. + * @param[in] aFrameLength The frame length in bytes. + * + */ + typedef void (*FrameHandler)(void *aContext, uint8_t *aFrame, uint16_t aFrameLength); + + /** + * This constructor initializes the decoder. + * + * @param[in] aOutBuf A pointer to the output buffer. + * @param[in] aOutLength Size of the output buffer in bytes. + * @param[in] aFrameHandler A pointer to a function that is called when a complete frame is received. + * @param[in] aContext A pointer to arbitrary context information. + * + */ + Decoder(uint8_t *aOutBuf, uint16_t aOutLength, FrameHandler aFrameHandler, void *aContext); + + /** + * This method streams bytes into the decoder. + * + * @param[in] aInBuf A pointer to the input buffer. + * @param[in] aInLength The number of bytes in @p aInBuf. + * + */ + void Decode(const uint8_t *aInBuf, uint16_t aInLength); + +private: + enum State + { + kStateNeedFlag = 0, + kStateNeedLenH, + kStateNeedLenL, + kStateNeedData, + }; + State mState; + + FrameHandler mFrameHandler; + void *mContext; + + uint8_t *mOutBuf; + uint16_t mOutOffset; + uint16_t mOutLength; + uint16_t mReadLength; +}; + +} // namespace Flen +} // namespace Thread + +#endif // FLEN_HPP_ diff --git a/src/ncp/hdlc.cpp b/src/ncp/hdlc.cpp new file mode 100644 index 000000000..aceb0bae2 --- /dev/null +++ b/src/ncp/hdlc.cpp @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2016, Nest Labs, Inc. + * 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. + */ + +/** + * @file + * This file implements an HDLC-lite encoder and decoder. + */ + +#include +#include + +namespace Thread { +namespace Hdlc { + +/** + * This method updates an FCS. + * + * @param[in] aFcs The FCS to update. + * @param[in] aByte The intput byte value. + * + * @returns The updated FCS. + * + */ +static uint16_t UpdateFcs(uint16_t aFcs, uint8_t aByte); + +enum +{ + kFlagSequence = 0x7e, ///< HDLC Flag value + kEscapeSequence = 0x7d, ///< HDLC Escape value +}; + +/** + * FCS lookup table + * + */ +enum +{ + kInitFcs = 0xffff, ///< Initial FCS value. + kGoodFcs = 0xf0b8, ///< Good FCS value. +}; + +uint16_t UpdateFcs(uint16_t aFcs, uint8_t aByte) +{ + static const uint16_t sFcsTable[256] = + { + 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, + 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, + 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, + 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, + 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, + 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, + 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, + 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, + 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, + 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, + 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, + 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, + 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, + 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, + 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, + 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, + 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, + 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, + 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, + 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, + 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, + 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, + 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, + 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, + 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, + 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, + 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, + 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, + 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, + 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, + 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, + 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 + }; + return (aFcs >> 8) ^ sFcsTable[(aFcs ^ aByte) & 0xff]; +} + +ThreadError Encoder::Init(uint8_t *aOutBuf, uint16_t &aOutLength) +{ + ThreadError error = kThreadError_None; + + mFcs = kInitFcs; + + VerifyOrExit(aOutLength > 0, error = kThreadError_NoBufs); + aOutBuf[0] = kFlagSequence; + aOutLength = 1; + +exit: + return error; +} + +ThreadError Encoder::Encode(uint8_t aInByte, uint8_t *aOutBuf, uint16_t aOutLength) +{ + ThreadError error = kThreadError_None; + + mFcs = UpdateFcs(mFcs, aInByte); + + if (aInByte == kFlagSequence || aInByte == kEscapeSequence) + { + VerifyOrExit(mOutOffset + 2 < aOutLength, error = kThreadError_NoBufs); + aOutBuf[mOutOffset++] = kEscapeSequence; + aOutBuf[mOutOffset++] = aInByte ^ 0x20; + } + else + { + VerifyOrExit(mOutOffset + 1 < aOutLength, error = kThreadError_NoBufs); + aOutBuf[mOutOffset++] = aInByte; + } + +exit: + return error; +} + +ThreadError Encoder::Encode(const uint8_t *aInBuf, uint16_t aInLength, uint8_t *aOutBuf, uint16_t &aOutLength) +{ + ThreadError error = kThreadError_None; + + mOutOffset = 0; + + for (int i = 0; i < aInLength; i++) + { + SuccessOrExit(error = Encode(aInBuf[i], aOutBuf, aOutLength)); + } + +exit: + aOutLength = mOutOffset; + return error; +} + +ThreadError Encoder::Finalize(uint8_t *aOutBuf, uint16_t &aOutLength) +{ + ThreadError error = kThreadError_None; + uint16_t fcs = mFcs; + + mOutOffset = 0; + + fcs ^= 0xffff; + + SuccessOrExit(error = Encode(fcs, aOutBuf, aOutLength)); + SuccessOrExit(error = Encode(fcs >> 8, aOutBuf, aOutLength)); + + VerifyOrExit(mOutOffset < aOutLength, error = kThreadError_NoBufs); + aOutBuf[mOutOffset++] = kFlagSequence; + + aOutLength = mOutOffset; + +exit: + return error; +} + +Decoder::Decoder(uint8_t *aOutBuf, uint16_t aOutLength, FrameHandler aFrameHandler, void *aContext) +{ + mState = kStateNoSync; + mFrameHandler = aFrameHandler; + mContext = aContext; + mOutBuf = aOutBuf; + mOutOffset = 0; + mOutLength = aOutLength; +} + +void Decoder::Decode(const uint8_t *aInBuf, uint16_t aInLength) +{ + uint8_t byte; + + for (int i = 0; i < aInLength; i++) + { + byte = aInBuf[i]; + + switch (mState) + { + case kStateNoSync: + if (byte == kFlagSequence) + { + mState = kStateSync; + mOutOffset = 0; + mFcs = kInitFcs; + } + + break; + + case kStateSync: + switch (byte) + { + case kEscapeSequence: + mState = kStateEscaped; + break; + + case kFlagSequence: + if (mOutOffset > 0) + { + if (mFcs == kGoodFcs) + { + mFrameHandler(mContext, mOutBuf, mOutOffset - 2); + } + + mOutOffset = 0; + mFcs = kInitFcs; + } + + break; + + default: + if (mOutOffset < mOutLength) + { + mFcs = UpdateFcs(mFcs, byte); + mOutBuf[mOutOffset++] = byte; + } + else + { + mState = kStateNoSync; + } + + break; + } + + break; + + case kStateEscaped: + if (mOutOffset < mOutLength) + { + byte ^= 0x20; + mFcs = UpdateFcs(mFcs, byte); + mOutBuf[mOutOffset++] = byte; + mState = kStateSync; + } + else + { + mState = kStateNoSync; + } + + + break; + } + } +} + +} // namespace Hdlc +} // namespace Thread diff --git a/src/ncp/hdlc.hpp b/src/ncp/hdlc.hpp new file mode 100644 index 000000000..8ee16a44b --- /dev/null +++ b/src/ncp/hdlc.hpp @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2016, Nest Labs, Inc. + * 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. + */ + +/** + * @file + * This file includes definitions for an HDLC-lite encoder and decoer. + */ + +#ifndef HDLC_HPP_ +#define HDLC_HPP_ + +#include +#include + +namespace Thread { + +/** + * @namespace Thread::Hdlc + * + * @brief + * This namespace includes definitions for the HDLC-lite encoder and decoder. + * + */ +namespace Hdlc { + +/** + * This class implements the HDLC-lite encoder. + * + */ +class Encoder +{ +public: + /** + * This method begins an HDLC frame and puts the initial bytes into @p aOutBuf. + * + * @param[in] aOutBuf A pointer to the output buffer. + * @param[inout] aOutLength On entry, the output buffer size; On exit, the output length. + * + * @retval kThreadError_None Successfully started the HDLC frame. + * @retval kThreadError_NoBufs Insufficient buffer space available to start the HDLC frame. + * + */ + ThreadError Init(uint8_t *aOutBuf, uint16_t &aOutLength); + + /** + * This method encodes the frame. + * + * @param[in] aInBuf A pointer to the input buffer. + * @param[in] aInLength The number of bytes in @p aInBuf to encode. + * @param[out] aOutBuf A pointer to the output buffer. + * @param[out] aOutLength On exit, the number of bytes placed in @p aOutBuf. + * + * @retval kThreadError_None Successfully encoded the HDLC frame. + * @retval kThreadError_NoBufs Insufficient buffer space available to encode the HDLC frame. + * + */ + ThreadError Encode(const uint8_t *aInBuf, uint16_t aInLength, uint8_t *aOutBuf, uint16_t &aOutLength); + + /** + * This method ends an HDLC frame and puts the initial bytes into @p aOutBuf. + * + * @param[in] aOutBuf A pointer to the output buffer. + * @param[inout] aOutLength On entry, the output buffer size; On exit, the output length. + * + * @retval kThreadError_None Successfully ended the HDLC frame. + * @retval kThreadError_NoBufs Insufficient buffer space available to end the HDLC frame. + * + */ + ThreadError Finalize(uint8_t *aOutBuf, uint16_t &aOutLength); + +private: + ThreadError Encode(uint8_t aInByte, uint8_t *aOutBuf, uint16_t aOutLength); + + uint16_t mOutOffset; + uint16_t mFcs; +}; + +/** + * This class implements the HDLC-lite decoder. + * + */ +class Decoder +{ +public: + /** + * This function pointer is called when a complete frame has been formed. + * + * @param[in] aContext A pointer to arbitrary context information. + * @param[in] aFrame A pointer to the frame. + * @param[in] aFrameLength The frame length in bytes. + * + */ + typedef void (*FrameHandler)(void *aContext, uint8_t *aFrame, uint16_t aFrameLength); + + /** + * This constructor initializes the decoder. + * + * @param[in] aOutBuf A pointer to the output buffer. + * @param[in] aOutLength Size of the output buffer in bytes. + * @param[in] aFrameHandler A pointer to a function that is called when a complete frame is received. + * @param[in] aContext A pointer to arbitrary context information. + * + */ + Decoder(uint8_t *aOutBuf, uint16_t aOutLength, FrameHandler aFrameHandler, void *aContext); + + /** + * This method streams bytes into the decoder. + * + * @param[in] aInBuf A pointer to the input buffer. + * @param[in] aInLength The number of bytes in @p aInBuf. + * + */ + void Decode(const uint8_t *aInBuf, uint16_t aInLength); + +private: + enum State + { + kStateNoSync = 0, + kStateSync, + kStateEscaped, + }; + State mState; + + FrameHandler mFrameHandler; + void *mContext; + + uint8_t *mOutBuf; + uint16_t mOutOffset; + uint16_t mOutLength; + + uint16_t mFcs; +}; + +} // namespace Hdlc +} // namespace Thread + +#endif // HDLC_HPP_ diff --git a/src/ncp/ncp.cpp b/src/ncp/ncp.cpp new file mode 100644 index 000000000..f89d01767 --- /dev/null +++ b/src/ncp/ncp.cpp @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2016, Nest Labs, Inc. + * 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. + */ + +/** + * @file + * This file implements an HDLC interface to the Thread stack. + */ + +#include +#include +#include + +namespace Thread { + +static Tasklet sSendDoneTask(&Ncp::SendDoneTask, NULL); +static Tasklet sReceiveTask(&Ncp::ReceiveTask, NULL); +static Ncp *sNcp; + +Ncp::Ncp(): + NcpBase(), + mFrameDecoder(mReceiveFrame, sizeof(mReceiveFrame), &HandleFrame, this) +{ + sNcp = this; +} + +ThreadError Ncp::Start() +{ + otPlatSerialEnable(); + return super_t::Start(); +} + +ThreadError Ncp::Stop() +{ + otPlatSerialDisable(); + return super_t::Stop(); +} + +uint16_t +Ncp::OutboundFrameGetRemaining(void) +{ + return static_cast(sizeof(mSendFrame) - (mSendFrameIter - mSendFrame)); +} + +ThreadError +Ncp::OutboundFrameBegin(void) +{ + ThreadError errorCode; + uint16_t outLength; + + mSendFrameIter = mSendFrame; + outLength = OutboundFrameGetRemaining(); + + errorCode = mFrameEncoder.Init(mSendFrameIter, outLength); + + if (errorCode == kThreadError_None) + { + mSendFrameIter += outLength; + } + + return errorCode; +} + +ThreadError +Ncp::OutboundFrameFeedData(const uint8_t *frame, uint16_t frameLength) +{ + ThreadError errorCode; + uint16_t outLength(OutboundFrameGetRemaining()); + + errorCode = mFrameEncoder.Encode(frame, frameLength, mSendFrameIter, outLength); + + if (errorCode == kThreadError_None) + { + mSendFrameIter += outLength; + } + + return errorCode; +} + +ThreadError +Ncp::OutboundFrameFeedMessage(Message &message) +{ + ThreadError errorCode; + uint16_t inLength; + uint16_t outLength; + uint8_t inBuf[16]; + + for (int offset = 0; offset < message.GetLength(); offset += sizeof(inBuf)) + { + outLength = OutboundFrameGetRemaining(); + inLength = message.Read(offset, sizeof(inBuf), inBuf); + + errorCode = OutboundFrameFeedData(inBuf, inLength); + + if (errorCode != kThreadError_None) + { + break; + } + } + + return errorCode; +} + +ThreadError +Ncp::OutboundFrameSend(void) +{ + ThreadError errorCode; + uint16_t outLength(OutboundFrameGetRemaining()); + + errorCode = mFrameEncoder.Finalize(mSendFrameIter, outLength); + + if (errorCode == kThreadError_None) + { + mSendFrameIter += outLength; + errorCode = otPlatSerialSend(mSendFrame, mSendFrameIter - mSendFrame); + } + + if (errorCode == kThreadError_None) + { + mSending = true; + } + + return errorCode; +} + +extern "C" void otPlatSerialSignalSendDone() +{ + sSendDoneTask.Post(); +} + +void Ncp::SendDoneTask(void *context) +{ + sNcp->SendDoneTask(); +} + +void Ncp::SendDoneTask() +{ + mSending = false; + + if (mSendMessage) + { + Message::Free(*mSendMessage); + mSendMessage = NULL; + } + + super_t::HandleSendDone(); +} + +extern "C" void otPlatSerialSignalReceive() +{ + sReceiveTask.Post(); +} + +void Ncp::ReceiveTask(void *context) +{ + sNcp->ReceiveTask(); +} + +void Ncp::ReceiveTask() +{ + const uint8_t *buf; + uint16_t bufLength; + + buf = otPlatSerialGetReceivedBytes(&bufLength); + + mFrameDecoder.Decode(buf, bufLength); + + otPlatSerialHandleReceiveDone(); +} + +void Ncp::HandleFrame(void *context, uint8_t *aBuf, uint16_t aBufLength) +{ + sNcp->HandleFrame(aBuf, aBufLength); +} + +void Ncp::HandleFrame(uint8_t *aBuf, uint16_t aBufLength) +{ + super_t::HandleReceive(aBuf, aBufLength); +} + +} // namespace Thread diff --git a/src/ncp/ncp.hpp b/src/ncp/ncp.hpp new file mode 100644 index 000000000..d995ede13 --- /dev/null +++ b/src/ncp/ncp.hpp @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2016, Nest Labs, Inc. + * 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. + */ + +/** + * @file + * This file contains definitions for an FLEN/HDLC interface to the OpenThread stack. + */ + +#ifndef NCP_HPP_ +#define NCP_HPP_ + +#include +#include +#include + +namespace Thread { + +class Ncp : public NcpBase +{ + typedef NcpBase super_t; + +public: + Ncp(); + + ThreadError Start(); + ThreadError Stop(); + + + virtual ThreadError OutboundFrameBegin(void); + virtual uint16_t OutboundFrameGetRemaining(void); + virtual ThreadError OutboundFrameFeedData(const uint8_t *frame, uint16_t frameLength); + virtual ThreadError OutboundFrameFeedMessage(Message &message); + virtual ThreadError OutboundFrameSend(void); + + static void HandleFrame(void *context, uint8_t *aBuf, uint16_t aBufLength); + static void SendDoneTask(void *context); + static void ReceiveTask(void *context); + +private: + void HandleFrame(uint8_t *aBuf, uint16_t aBufLength); + void SendDoneTask(); + void ReceiveTask(); + + Hdlc::Encoder mFrameEncoder; + Hdlc::Decoder mFrameDecoder; + + uint8_t mSendFrame[1500]; + uint8_t mReceiveFrame[1500]; + uint8_t *mSendFrameIter; + Message *mSendMessage; +}; + +} // namespace Thread + +#endif // NCP_HPP_ diff --git a/src/ncp/ncp_base.cpp b/src/ncp/ncp_base.cpp new file mode 100644 index 000000000..b4b6d3ad0 --- /dev/null +++ b/src/ncp/ncp_base.cpp @@ -0,0 +1,1572 @@ +/* + * Copyright (c) 2016, Nest Labs, Inc. + * 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. + */ + +/** + * @file + * This file implements a Spinel interface to the OpenThread stack. + */ + +#include +#include +#include +#include +#include +#include +#include + +namespace Thread { + +extern ThreadNetif *sThreadNetif; + +static spinel_status_t ThreadErrorToSpinelStatus(ThreadError error) +{ + spinel_status_t ret; + + switch (error) + { + case kThreadError_None: + ret = SPINEL_STATUS_OK; + break; + + case kThreadError_Failed: + ret = SPINEL_STATUS_FAILURE; + break; + + case kThreadError_Drop: + ret = SPINEL_STATUS_DROPPED; + break; + + case kThreadError_NoBufs: + ret = SPINEL_STATUS_NOMEM; + break; + + case kThreadError_Busy: + ret = SPINEL_STATUS_BUSY; + break; + + case kThreadError_Parse: + ret = SPINEL_STATUS_PARSE_ERROR; + break; + + case kThreadError_InvalidArgs: + ret = SPINEL_STATUS_INVALID_ARGUMENT; + break; + + case kThreadError_NotImplemented: + ret = SPINEL_STATUS_UNIMPLEMENTED; + break; + + case kThreadError_InvalidState: + ret = SPINEL_STATUS_INVALID_STATE; + break; + + default: + ret = SPINEL_STATUS_FAILURE; + break; + } + + return ret; +} + +NcpBase::NcpBase(): + mNetifHandler(&HandleUnicastAddressesChanged, this), + mUpdateAddressesTask(&RunUpdateAddressesTask, this) +{ + mChannelMask = (0xFFFF << 11); // Default to all channels +} + +ThreadError NcpBase::Start() +{ + assert(sThreadNetif != NULL); + sThreadNetif->RegisterHandler(mNetifHandler); + Ip6::Ip6::SetNcpReceivedHandler(&HandleReceivedDatagram, this); + return kThreadError_None; +} + +ThreadError NcpBase::Stop() +{ + return kThreadError_None; +} + +void NcpBase::HandleReceivedDatagram(void *context, Message &message) +{ + NcpBase *obj = reinterpret_cast(context); + obj->HandleReceivedDatagram(message); +} + +void NcpBase::HandleReceivedDatagram(Message &message) +{ + ThreadError errorCode; + + if (mSending == false) + { + errorCode = OutboundFrameBegin(); + + if (errorCode == kThreadError_None) + { + errorCode = OutboundFrameFeedPacked( + "CiiS", + SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, + SPINEL_CMD_PROP_VALUE_IS, + SPINEL_PROP_STREAM_NET, + message.GetLength() + ); + } + + if (errorCode == kThreadError_None) + { + errorCode = OutboundFrameFeedMessage(message); + } + + // TODO: Append any metadata here! + + if (errorCode == kThreadError_None) + { + errorCode = OutboundFrameSend(); + } + + if (errorCode != kThreadError_None) + { + SendLastStatus(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, SPINEL_STATUS_DROPPED); + } + } + else + { + if (mSendQueue.Enqueue(message) != kThreadError_None) + { + Message::Free(message); + } + } +} + +static NcpBase *gActiveScanContextHack = NULL; + +void NcpBase::HandleActiveScanResult_Jump(otActiveScanResult *result) +{ + if (gActiveScanContextHack) + { + gActiveScanContextHack->HandleActiveScanResult(result); + } +} + +void NcpBase::HandleActiveScanResult(otActiveScanResult *result) +{ + VerifyOrExit(mSending == false, ;); + + if (result) + { + uint8_t flags = (result->mVersion << SPINEL_BEACON_THREAD_FLAG_VERSION_SHIFT); + + if (result->mIsJoinable) + { + flags |= SPINEL_BEACON_THREAD_FLAG_JOINABLE; + } + + if (result->mIsNative) + { + flags |= SPINEL_BEACON_THREAD_FLAG_NATIVE; + } + + //chan,rssi,(laddr,saddr,panid,lqi),(proto,flags,networkid,xpanid) [icT(ESSC)T(iCUD.).] + NcpBase::SendPropteryUpdate( + SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, + SPINEL_CMD_PROP_VALUE_INSERTED, + SPINEL_PROP_MAC_SCAN_BEACON, + "CcT(ESSC)T(iCUD.).", + result->mChannel,//chan + result->mRssi,//rssi + result->mExtAddress.m8,// laddr + 0xFFFF, // saddr, Not given + result->mPanId,//panid + 0xFF,//lqi, not given + SPINEL_PROTOCOL_TYPE_THREAD,//proto + flags, + result->mNetworkName,//networkid + result->mExtPanId, sizeof(result->mExtPanId) //xpanid + ); + } + else + { + SendPropteryUpdate( + SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, + SPINEL_CMD_PROP_VALUE_IS, + SPINEL_PROP_MAC_SCAN_STATE, + SPINEL_DATATYPE_UINT8_S, + SPINEL_SCAN_STATE_IDLE + ); + } + +exit: + return; +} + + +void NcpBase::HandleUnicastAddressesChanged(void *context) +{ + NcpBase *obj = reinterpret_cast(context); + obj->mUpdateAddressesTask.Post(); +} + +void NcpBase::RunUpdateAddressesTask(void *context) +{ + NcpBase *obj = reinterpret_cast(context); + obj->RunUpdateAddressesTask(); +} + +void NcpBase::RunUpdateAddressesTask() +{ + VerifyOrExit(mSending == false, ;); + + // It would really be preferable to have inserted/removed notifications + // for the individual addresses, rather than a single "changed" event. + HandleCommandPropertyGet(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, SPINEL_PROP_IPV6_ADDRESS_TABLE); + + HandleCommandPropertyGet(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, SPINEL_PROP_NET_STATE); + +exit: + return; +} + +// ============================================================ +// Serial channel message callbacks +// ============================================================ + +void NcpBase::HandleReceive(void *context, const uint8_t *buf, uint16_t bufLength) +{ + NcpBase *obj = reinterpret_cast(context); + obj->HandleReceive(buf, bufLength); +} + +void NcpBase::HandleReceive(const uint8_t *buf, uint16_t bufLength) +{ + uint8_t header = 0; + unsigned int command = 0; + spinel_ssize_t parsedLength; + const uint8_t *arg_ptr = NULL; + unsigned int arg_len = 0; + + parsedLength = spinel_datatype_unpack(buf, bufLength, "CiD", &header, &command, &arg_ptr, &arg_len); + + if (parsedLength == bufLength) + { + HandleCommand(header, command, arg_ptr, static_cast(arg_len)); + } + else + { + SendLastStatus(header, SPINEL_STATUS_PARSE_ERROR); + } +} + +void NcpBase::HandleCommand(uint8_t header, unsigned int command, const uint8_t *arg_ptr, uint16_t arg_len) +{ + unsigned int propKey = 0; + spinel_ssize_t parsedLength; + const uint8_t *value_ptr; + unsigned int value_len; + + if ((SPINEL_HEADER_FLAG & header) != SPINEL_HEADER_FLAG) + { + // Skip + return; + } + + // We only support IID zero for now. + if (SPINEL_HEADER_GET_IID(header) != 0) + { + SendLastStatus(header, SPINEL_STATUS_INVALID_INTERFACE); + return; + } + + switch (command) + { + case SPINEL_CMD_NOOP: + SendLastStatus(header, SPINEL_STATUS_OK); + break; + + case SPINEL_CMD_RESET: + // TODO: Figure out how to actually perform a reset. + SendLastStatus(0, SPINEL_STATUS_RESET_SOFTWARE); + break; + + case SPINEL_CMD_PROP_VALUE_GET: + parsedLength = spinel_datatype_unpack(arg_ptr, arg_len, "i", &propKey); + + if (parsedLength > 0) + { + HandleCommandPropertyGet(header, static_cast(propKey)); + } + else + { + SendLastStatus(header, SPINEL_STATUS_PARSE_ERROR); + } + + break; + + case SPINEL_CMD_PROP_VALUE_SET: + parsedLength = spinel_datatype_unpack(arg_ptr, arg_len, "iD", &propKey, &value_ptr, &value_len); + + if (parsedLength == arg_len) + { + HandleCommandPropertySet(header, static_cast(propKey), value_ptr, value_len); + } + else + { + SendLastStatus(header, SPINEL_STATUS_PARSE_ERROR); + } + + break; + + case SPINEL_CMD_PROP_VALUE_INSERT: + parsedLength = spinel_datatype_unpack(arg_ptr, arg_len, "iD", &propKey, &value_ptr, &value_len); + + if (parsedLength == arg_len) + { + HandleCommandPropertyInsert(header, static_cast(propKey), value_ptr, value_len); + } + else + { + SendLastStatus(header, SPINEL_STATUS_PARSE_ERROR); + } + + break; + + case SPINEL_CMD_PROP_VALUE_REMOVE: + parsedLength = spinel_datatype_unpack(arg_ptr, arg_len, "iD", &propKey, &value_ptr, &value_len); + + if (parsedLength == arg_len) + { + HandleCommandPropertyRemove(header, static_cast(propKey), value_ptr, value_len); + } + else + { + SendLastStatus(header, SPINEL_STATUS_PARSE_ERROR); + } + + break; + + default: + SendLastStatus(header, SPINEL_STATUS_INVALID_COMMAND); + break; + } +} + +void NcpBase::HandleCommandPropertyGet(uint8_t header, spinel_prop_key_t key) +{ + const uint8_t *ptr; + uint8_t len, tmp; + + if (mSending) + { + // If we are currently sending, can can queue up to one + // property get that we can execute immediately after + // the send is complete. + if (mQueuedGetHeader == 0) + { + mQueuedGetHeader = header; + mQueuedGetKey = key; + } + + return; + } + + switch (key) + { + case SPINEL_PROP_LOCK: + case SPINEL_PROP_MAC_SCAN_MASK: + case SPINEL_PROP_PHY_TX_POWER: + SendLastStatus(header, SPINEL_STATUS_UNIMPLEMENTED); + break; + + case SPINEL_PROP_LAST_STATUS: + SendPropteryUpdate(header, SPINEL_CMD_PROP_VALUE_IS, key, SPINEL_DATATYPE_UINT_PACKED_S, mLastStatus); + break; + + case SPINEL_PROP_PROTOCOL_VERSION: + SendPropteryUpdate( + header, + SPINEL_CMD_PROP_VALUE_IS, + key, + SPINEL_DATATYPE_UINT_PACKED_S SPINEL_DATATYPE_UINT_PACKED_S SPINEL_DATATYPE_UINT_PACKED_S, + SPINEL_PROTOCOL_TYPE_THREAD, + SPINEL_PROTOCOL_VERSION_THREAD_MAJOR, + SPINEL_PROTOCOL_VERSION_THREAD_MINOR + ); + break; + + case SPINEL_PROP_CAPABILITIES: + SendPropteryUpdate( + header, + SPINEL_CMD_PROP_VALUE_IS, + key, + SPINEL_DATATYPE_UINT_PACKED_S + SPINEL_DATATYPE_UINT_PACKED_S + SPINEL_DATATYPE_UINT_PACKED_S, + SPINEL_CAP_ROLE_ROUTER, + SPINEL_CAP_NET_THREAD_1_0, + SPINEL_CAP_802_15_4_2450MHZ_OQPSK + ); + break; + + case SPINEL_PROP_NCP_VERSION: + SendPropteryUpdate( + header, + SPINEL_CMD_PROP_VALUE_IS, + key, + SPINEL_DATATYPE_UTF8_S, + PACKAGE_NAME "/" PACKAGE_VERSION "; " __DATE__ " " __TIME__ + ); + break; + + case SPINEL_PROP_INTERFACE_COUNT: + SendPropteryUpdate( + header, + SPINEL_CMD_PROP_VALUE_IS, + key, + SPINEL_DATATYPE_UINT8_S, + 1 // Only one interface for now + ); + break; + + case SPINEL_PROP_POWER_STATE: + // Always online at the moment + SendPropteryUpdate( + header, + SPINEL_CMD_PROP_VALUE_IS, + key, + SPINEL_DATATYPE_UINT8_S, + SPINEL_POWER_STATE_ONLINE + ); + break; + + case SPINEL_PROP_NET_NETWORK_NAME: + SendPropteryUpdate( + header, + SPINEL_CMD_PROP_VALUE_IS, + key, + SPINEL_DATATYPE_UTF8_S, + otGetNetworkName() + ); + break; + + case SPINEL_PROP_MAC_15_4_PANID: + SendPropteryUpdate( + header, + SPINEL_CMD_PROP_VALUE_IS, + key, + SPINEL_DATATYPE_UINT16_S, + otGetPanId() + ); + break; + + case SPINEL_PROP_PHY_FREQ: + { + uint32_t freq_khz; + uint8_t chan = otGetChannel(); + + if (chan == 0) + { + freq_khz = 868300; + } + else if (chan < 11) + { + freq_khz = 906000 - (2000 * 1) + 2000 * (chan); + } + else if (chan < 26) + { + freq_khz = 2405 - (5000 * 11) + 5000 * (chan); + } + else + { + SendLastStatus(header, SPINEL_STATUS_FAILURE); + return; + } + + SendPropteryUpdate( + header, + SPINEL_CMD_PROP_VALUE_IS, + key, + SPINEL_DATATYPE_UINT32_S, + &freq_khz + ); + break; + } + + case SPINEL_PROP_PHY_CHAN_SUPPORTED: + { + static const uint8_t supported_channels[] = + { + 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 26 + }; + SendPropteryUpdate( + header, + SPINEL_CMD_PROP_VALUE_IS, + key, + SPINEL_DATATYPE_DATA_S, + supported_channels, + sizeof(supported_channels) + ); + break; + } + + case SPINEL_PROP_PHY_CHAN: + SendPropteryUpdate( + header, + SPINEL_CMD_PROP_VALUE_IS, + key, + SPINEL_DATATYPE_UINT8_S, + otGetChannel() + ); + break; + + case SPINEL_PROP_HWADDR: + case SPINEL_PROP_MAC_15_4_LADDR: + // TODO: Figure out how these cases should be differentiated. + SendPropteryUpdate( + header, + SPINEL_CMD_PROP_VALUE_IS, + key, + SPINEL_DATATYPE_EUI64_S, + otGetExtendedAddress() + ); + break; + + case SPINEL_PROP_MAC_15_4_SADDR: + SendPropteryUpdate( + header, + SPINEL_CMD_PROP_VALUE_IS, + key, + SPINEL_DATATYPE_UINT16_S, + sThreadNetif->GetMac().GetShortAddress() + ); + break; + + case SPINEL_PROP_NET_XPANID: + SendPropteryUpdate( + header, + SPINEL_CMD_PROP_VALUE_IS, + key, + SPINEL_DATATYPE_DATA_S, + otGetExtendedPanId(), + sizeof(spinel_net_xpanid_t) + ); + break; + + case SPINEL_PROP_NET_MASTER_KEY: + ptr = otGetMasterKey(&len); + SendPropteryUpdate( + header, + SPINEL_CMD_PROP_VALUE_IS, + key, + SPINEL_DATATYPE_DATA_S, + ptr, + len + ); + break; + + case SPINEL_PROP_NET_KEY_SEQUENCE: + SendPropteryUpdate( + header, + SPINEL_CMD_PROP_VALUE_IS, + key, + SPINEL_DATATYPE_UINT32_S, + otGetKeySequenceCounter() + ); + break; + + case SPINEL_PROP_PHY_RSSI: + SendPropteryUpdate( + header, + SPINEL_CMD_PROP_VALUE_IS, + key, + SPINEL_DATATYPE_INT8_S, + otPlatRadioGetNoiseFloor() + ); + break; + + case SPINEL_PROP_NET_PARTITION_ID: + SendPropteryUpdate( + header, + SPINEL_CMD_PROP_VALUE_IS, + key, + SPINEL_DATATYPE_UINT32_S, + otGetPartitionId() + ); + break; + + case SPINEL_PROP_NET_ENABLED: + tmp = (otGetDeviceRole() != kDeviceRoleDisabled); + SendPropteryUpdate( + header, + SPINEL_CMD_PROP_VALUE_IS, + key, + SPINEL_DATATYPE_UINT8_S, + tmp + ); + break; + + case SPINEL_PROP_NET_STATE: + switch (otGetDeviceRole()) + { + case kDeviceRoleDisabled: + tmp = SPINEL_NET_STATE_OFFLINE; + break; + + case kDeviceRoleDetached: + tmp = SPINEL_NET_STATE_DETACHED; + break; + + case kDeviceRoleChild: + case kDeviceRoleRouter: + case kDeviceRoleLeader: + tmp = SPINEL_NET_STATE_ATTACHED; + break; + } + + SendPropteryUpdate( + header, + SPINEL_CMD_PROP_VALUE_IS, + key, + SPINEL_DATATYPE_UINT8_S, + tmp + ); + break; + + case SPINEL_PROP_NET_ROLE: + switch (otGetDeviceRole()) + { + case kDeviceRoleDisabled: + case kDeviceRoleDetached: + tmp = SPINEL_NET_ROLE_NONE; + break; + + case kDeviceRoleChild: + tmp = SPINEL_NET_ROLE_CHILD; + break; + + case kDeviceRoleRouter: + tmp = SPINEL_NET_ROLE_ROUTER; + break; + + case kDeviceRoleLeader: + tmp = SPINEL_NET_ROLE_LEADER; + break; + } + + SendPropteryUpdate( + header, + SPINEL_CMD_PROP_VALUE_IS, + key, + SPINEL_DATATYPE_UINT8_S, + tmp + ); + break; + + case SPINEL_PROP_THREAD_LEADER: + { + ThreadError errorCode; + Ip6::Address address; + errorCode = sThreadNetif->GetMle().GetLeaderAddress(address); + + if (errorCode) + { + SendLastStatus(header, ThreadErrorToSpinelStatus(errorCode)); + } + else + { + SendPropteryUpdate( + header, + SPINEL_CMD_PROP_VALUE_IS, + key, + SPINEL_DATATYPE_IPv6ADDR_S, + &address + ); + } + + break; + } + + case SPINEL_PROP_IPV6_ML_PREFIX: + { + const uint8_t *ml_prefix = sThreadNetif->GetMle().GetMeshLocalPrefix(); + + if (ml_prefix) + { + SendPropteryUpdate( + header, + SPINEL_CMD_PROP_VALUE_IS, + key, + SPINEL_DATATYPE_IPv6ADDR_S SPINEL_DATATYPE_UINT8_S, + ml_prefix, + 64 + ); + } + else + { + SendPropteryUpdate( + header, + SPINEL_CMD_PROP_VALUE_IS, + key, + SPINEL_DATATYPE_VOID_S + ); + } + + break; + } + + case SPINEL_PROP_IPV6_ADDRESS_TABLE: + HandleCommandPropertyGetAddressList(header); + break; + + case SPINEL_PROP_IPV6_ROUTE_TABLE: + HandleCommandPropertyGetRoutingTable(header); + break; + + case SPINEL_PROP_MAC_SCAN_STATE: + if (otActiveScanInProgress()) + { + SendPropteryUpdate( + header, + SPINEL_CMD_PROP_VALUE_IS, + key, + SPINEL_DATATYPE_UINT8_S, + SPINEL_SCAN_STATE_BEACON + ); + } + else + { + SendPropteryUpdate( + header, + SPINEL_CMD_PROP_VALUE_IS, + key, + SPINEL_DATATYPE_UINT8_S, + SPINEL_SCAN_STATE_IDLE + ); + } + + break; + + case SPINEL_PROP_STREAM_NET: + case SPINEL_PROP_STREAM_NET_INSECURE: + case SPINEL_PROP_STREAM_DEBUG: + case SPINEL_PROP_STREAM_RAW: + case SPINEL_PROP_MAC_SCAN_BEACON: + // These properties don't have a "Getter" + SendLastStatus(header, SPINEL_STATUS_FAILURE); + break; + + default: + SendLastStatus(header, SPINEL_STATUS_PROPERTY_NOT_FOUND); + break; + } +} + +void NcpBase::HandleCommandPropertySet(uint8_t header, spinel_prop_key_t key, const uint8_t *value_ptr, + uint16_t value_len) +{ + const uint8_t *ptr = NULL; + unsigned int i = 0; + uint16_t tmp; + const char *string = NULL; + spinel_ssize_t parsedLength; + spinel_size_t len; + ThreadError errorCode = kThreadError_None; + + switch (key) + { + case SPINEL_PROP_LAST_STATUS: + case SPINEL_PROP_PROTOCOL_VERSION: + case SPINEL_PROP_CAPABILITIES: + case SPINEL_PROP_NCP_VERSION: + case SPINEL_PROP_STREAM_DEBUG: + case SPINEL_PROP_MAC_SCAN_BEACON: + case SPINEL_PROP_NET_PARTITION_ID: + case SPINEL_PROP_PHY_FREQ: + case SPINEL_PROP_IPV6_ADDRESS_TABLE: + case SPINEL_PROP_IPV6_ROUTE_TABLE: + case SPINEL_PROP_PHY_RSSI: + case SPINEL_PROP_INTERFACE_COUNT: + // These properties don't have a "Setter" + SendLastStatus(header, SPINEL_STATUS_FAILURE); + break; + + case SPINEL_PROP_MAC_SCAN_MASK: + case SPINEL_PROP_POWER_STATE: + case SPINEL_PROP_PHY_TX_POWER: + SendLastStatus(header, SPINEL_STATUS_UNIMPLEMENTED); + break; + + case SPINEL_PROP_IPV6_ML_PREFIX: + parsedLength = spinel_datatype_unpack( + value_ptr, + value_len, + SPINEL_DATATYPE_IPv6ADDR_S, + &ptr + ); + + if (parsedLength > 0) + { + errorCode = sThreadNetif->GetMle().SetMeshLocalPrefix(ptr); + HandleCommandPropertyGet(header, key); + } + else + { + errorCode = kThreadError_Parse; + } + + if (errorCode == kThreadError_None) + { + HandleCommandPropertyGet(header, key); + } + else + { + SendLastStatus(header, ThreadErrorToSpinelStatus(errorCode)); + } + + break; + + case SPINEL_PROP_NET_ENABLED: + { + bool value = false; + + parsedLength = spinel_datatype_unpack( + value_ptr, + value_len, + SPINEL_DATATYPE_BOOL_S, + &value + ); + + if (parsedLength > 0) + { + if (value == false) + { + errorCode = otDisable(); + } + else + { + errorCode = otEnable(); + } + } + else + { + errorCode = kThreadError_Parse; + } + + if (errorCode == kThreadError_None) + { + HandleCommandPropertyGet(header, key); + } + else + { + SendLastStatus(header, ThreadErrorToSpinelStatus(errorCode)); + } + + break; + } + + case SPINEL_PROP_NET_STATE: + parsedLength = spinel_datatype_unpack( + value_ptr, + value_len, + SPINEL_DATATYPE_UINT_PACKED_S, + &i + ); + + if (parsedLength > 0) + { + switch (i) + { + case SPINEL_NET_STATE_OFFLINE: + if (otGetDeviceRole() != kDeviceRoleDisabled) + { + errorCode = otDisable(); + } + + break; + + case SPINEL_NET_STATE_DETACHED: + if (otGetDeviceRole() == kDeviceRoleDisabled) + { + errorCode = otEnable(); + + if (errorCode == kThreadError_None) + { + errorCode = otBecomeDetached(); + } + } + else if (otGetDeviceRole() != kDeviceRoleDetached) + { + errorCode = otBecomeDetached(); + } + + break; + + case SPINEL_NET_STATE_ATTACHING: + case SPINEL_NET_STATE_ATTACHED: + if (otGetDeviceRole() == kDeviceRoleDisabled) + { + errorCode = otEnable(); + } + + if (otGetDeviceRole() == kDeviceRoleDetached) + { + errorCode = otBecomeRouter(); + + if (errorCode == kThreadError_None) + { + SendPropteryUpdate( + header, + SPINEL_CMD_PROP_VALUE_IS, + key, + SPINEL_DATATYPE_UINT8_S, + SPINEL_NET_STATE_ATTACHING + ); + return; + } + } + + break; + } + + if (errorCode == kThreadError_None) + { + HandleCommandPropertyGet(header, key); + } + else + { + SendLastStatus(header, ThreadErrorToSpinelStatus(errorCode)); + } + } + else + { + SendLastStatus(header, SPINEL_STATUS_PARSE_ERROR); + } + + break; + + case SPINEL_PROP_NET_ROLE: + parsedLength = spinel_datatype_unpack( + value_ptr, + value_len, + SPINEL_DATATYPE_UINT_PACKED_S, + &i + ); + + if (parsedLength > 0) + { + switch (i) + { + case SPINEL_NET_ROLE_NONE: + errorCode = kThreadError_InvalidArgs; + break; + + case SPINEL_NET_ROLE_ROUTER: + errorCode = otBecomeRouter(); + break; + + case SPINEL_NET_ROLE_LEADER: + errorCode = otBecomeLeader(); + break; + + case SPINEL_NET_ROLE_CHILD: + errorCode = otBecomeChild(kMleAttachAnyPartition); + break; + } + + if (errorCode == kThreadError_None) + { + HandleCommandPropertyGet(header, key); + } + else + { + SendLastStatus(header, ThreadErrorToSpinelStatus(errorCode)); + } + } + else + { + SendLastStatus(header, SPINEL_STATUS_PARSE_ERROR); + } + + break; + + case SPINEL_PROP_MAC_SCAN_STATE: + parsedLength = spinel_datatype_unpack( + value_ptr, + value_len, + SPINEL_DATATYPE_UINT_PACKED_S, + &i + ); + + if (parsedLength > 0) + { + switch (i) + { + case SPINEL_SCAN_STATE_IDLE: + errorCode = kThreadError_None; + break; + + case SPINEL_SCAN_STATE_BEACON: + gActiveScanContextHack = this; + errorCode = otActiveScan((mChannelMask >> kPhyMinChannel), 200, &HandleActiveScanResult_Jump); + break; + + case SPINEL_SCAN_STATE_ENERGY: + errorCode = kThreadError_NotImplemented; + break; + + default: + errorCode = kThreadError_InvalidArgs; + break; + } + + if (errorCode == kThreadError_None) + { + HandleCommandPropertyGet(header, key); + } + else + { + SendLastStatus(header, ThreadErrorToSpinelStatus(errorCode)); + } + } + else + { + SendLastStatus(header, SPINEL_STATUS_PARSE_ERROR); + } + + break; + + case SPINEL_PROP_STREAM_NET_INSECURE: + SendLastStatus(header, SPINEL_STATUS_UNIMPLEMENTED); + break; + + case SPINEL_PROP_STREAM_NET: + { + const uint8_t *frame_ptr(NULL); + unsigned int frame_len(0); + const uint8_t *meta_ptr(NULL); + unsigned int meta_len(0); + Message *message(Ip6::Ip6::NewMessage(0)); + + if (message == NULL) + { + errorCode = kThreadError_NoBufs; + } + else + { + parsedLength = spinel_datatype_unpack( + value_ptr, + value_len, + SPINEL_DATATYPE_DATA_S SPINEL_DATATYPE_DATA_S, + &frame_ptr, + &frame_len, + &meta_ptr, + &meta_len + ); + + // We ignore metadata for now. + (void)meta_ptr; + (void)meta_len; + + errorCode = message->Append(frame_ptr, frame_len); + } + + if (errorCode == kThreadError_None) + { + errorCode = Ip6::Ip6::HandleDatagram(*message, NULL, sThreadNetif->GetInterfaceId(), NULL, true); + } + + if (errorCode == kThreadError_None) + { + if (SPINEL_HEADER_GET_TID(header) != 0) + { + // Only send a successful status update if + // there was a transaction id in the header. + SendLastStatus(header, SPINEL_STATUS_OK); + } + } + else + { + SendLastStatus(header, ThreadErrorToSpinelStatus(errorCode)); + } + + break; + } + + + case SPINEL_PROP_NET_NETWORK_NAME: + parsedLength = spinel_datatype_unpack( + value_ptr, + value_len, + SPINEL_DATATYPE_UTF8_S, + &string + ); + + if ((parsedLength > 0) && (string != NULL)) + { + errorCode = otSetNetworkName(string); + + if (errorCode == kThreadError_None) + { + HandleCommandPropertyGet(header, key); + } + else + { + SendLastStatus(header, ThreadErrorToSpinelStatus(errorCode)); + } + } + else + { + SendLastStatus(header, SPINEL_STATUS_PARSE_ERROR); + } + + break; + + case SPINEL_PROP_PHY_CHAN: + parsedLength = spinel_datatype_unpack( + value_ptr, + value_len, + SPINEL_DATATYPE_UINT_PACKED_S, + &i + ); + + if (parsedLength > 0) + { + errorCode = otSetChannel(static_cast(i)); + + if (errorCode == kThreadError_None) + { + HandleCommandPropertyGet(header, key); + } + else + { + SendLastStatus(header, ThreadErrorToSpinelStatus(errorCode)); + } + } + else + { + SendLastStatus(header, SPINEL_STATUS_PARSE_ERROR); + } + + break; + + + case SPINEL_PROP_MAC_15_4_PANID: + parsedLength = spinel_datatype_unpack( + value_ptr, + value_len, + SPINEL_DATATYPE_UINT16_S, + &tmp + ); + + if (parsedLength > 0) + { + errorCode = otSetPanId(tmp); + + if (errorCode == kThreadError_None) + { + HandleCommandPropertyGet(header, key); + } + else + { + SendLastStatus(header, ThreadErrorToSpinelStatus(errorCode)); + } + } + else + { + SendLastStatus(header, SPINEL_STATUS_PARSE_ERROR); + } + + break; + + + case SPINEL_PROP_NET_XPANID: + parsedLength = spinel_datatype_unpack( + value_ptr, + value_len, + SPINEL_DATATYPE_DATA_S, + &ptr, + &len + ); + + if ((parsedLength > 0) && (len == sizeof(spinel_net_xpanid_t))) + { + otSetExtendedPanId(ptr); + HandleCommandPropertyGet(header, key); + } + else + { + SendLastStatus(header, SPINEL_STATUS_PARSE_ERROR); + } + + break; + + case SPINEL_PROP_NET_MASTER_KEY: + parsedLength = spinel_datatype_unpack( + value_ptr, + value_len, + SPINEL_DATATYPE_DATA_S, + &ptr, + &len + ); + + if ((parsedLength > 0) && (len < 100)) + { + errorCode = otSetMasterKey(ptr, static_cast(len)); + + if (errorCode == kThreadError_None) + { + HandleCommandPropertyGet(header, key); + } + else + { + SendLastStatus(header, ThreadErrorToSpinelStatus(errorCode)); + } + } + else + { + SendLastStatus(header, SPINEL_STATUS_PARSE_ERROR); + } + + break; + + case SPINEL_PROP_NET_KEY_SEQUENCE: + parsedLength = spinel_datatype_unpack( + value_ptr, + value_len, + SPINEL_DATATYPE_UINT32_S, + &i + ); + + if (parsedLength > 0) + { + otSetKeySequenceCounter(i); + HandleCommandPropertyGet(header, key); + } + else + { + SendLastStatus(header, SPINEL_STATUS_PARSE_ERROR); + } + + break; + + default: + SendLastStatus(header, SPINEL_STATUS_PROPERTY_NOT_FOUND); + break; + } + +} + +void NcpBase::HandleCommandPropertyGetAddressList(uint8_t header) +{ + ThreadError errorCode; + + errorCode = OutboundFrameBegin(); + + if (errorCode == kThreadError_None) + { + errorCode = OutboundFrameFeedPacked("Cii", header, SPINEL_CMD_PROP_VALUE_IS, SPINEL_PROP_IPV6_ADDRESS_TABLE); + } + + for (const otNetifAddress *address = otGetUnicastAddresses(); address; address = address->mNext) + { + if (errorCode != kThreadError_None) + { + break; + } + + errorCode = OutboundFrameFeedPacked( + "T(6CLL).", + &address->mAddress, + address->mPrefixLength, + address->mPreferredLifetime, + address->mValidLifetime + ); + } + + if (errorCode == kThreadError_None) + { + errorCode = OutboundFrameSend(); + } + + if (errorCode != kThreadError_None) + { + SendLastStatus(header, SPINEL_STATUS_INTERNAL_ERROR); + } +} + +void NcpBase::HandleCommandPropertyGetRoutingTable(uint8_t header) +{ + SendLastStatus(header, SPINEL_STATUS_UNIMPLEMENTED); +} + +void NcpBase::HandleCommandPropertyInsert(uint8_t header, spinel_prop_key_t key, const uint8_t *value_ptr, + uint16_t value_len) +{ + spinel_ssize_t parsedLength; + ThreadError errorCode = kThreadError_None; + + switch (key) + { + case SPINEL_PROP_IPV6_ADDRESS_TABLE: + { + otNetifAddress netif_addr = {}; + otIp6Address *addr_ptr; + + parsedLength = spinel_datatype_unpack( + value_ptr, + value_len, + "6CLL", + &addr_ptr, + &netif_addr.mPrefixLength, + &netif_addr.mPreferredLifetime, + &netif_addr.mValidLifetime + ); + + if (parsedLength > 0) + { + netif_addr.mAddress = *addr_ptr; + errorCode = otAddUnicastAddress(&netif_addr); + + if (errorCode == kThreadError_None) + { + SendPropteryUpdate( + header, + SPINEL_CMD_PROP_VALUE_INSERTED, + key, + value_ptr, + value_len + ); + } + else + { + SendLastStatus(header, ThreadErrorToSpinelStatus(errorCode)); + } + } + else + { + SendLastStatus(header, SPINEL_STATUS_PARSE_ERROR); + } + + break; + } + + default: + SendLastStatus(header, SPINEL_STATUS_FAILURE); + break; + } +} + +void NcpBase::HandleCommandPropertyRemove(uint8_t header, spinel_prop_key_t key, const uint8_t *value_ptr, + uint16_t value_len) +{ + spinel_ssize_t parsedLength; + ThreadError errorCode = kThreadError_None; + + switch (key) + { + case SPINEL_PROP_IPV6_ADDRESS_TABLE: + { + otNetifAddress netif_addr = {}; + otIp6Address *addr_ptr; + + parsedLength = spinel_datatype_unpack( + value_ptr, + value_len, + "6CLL", + &addr_ptr, + &netif_addr.mPrefixLength, + &netif_addr.mPreferredLifetime, + &netif_addr.mValidLifetime + ); + + if (parsedLength > 0) + { + netif_addr.mAddress = *addr_ptr; + errorCode = otRemoveUnicastAddress(&netif_addr); + + if (errorCode == kThreadError_None) + { + SendPropteryUpdate( + header, + SPINEL_CMD_PROP_VALUE_REMOVED, + key, + value_ptr, + value_len + ); + } + else + { + SendLastStatus(header, ThreadErrorToSpinelStatus(errorCode)); + } + } + else + { + SendLastStatus(header, SPINEL_STATUS_PARSE_ERROR); + } + + break; + } + + default: + SendLastStatus(header, SPINEL_STATUS_FAILURE); + break; + } +} + +void NcpBase::SendLastStatus(uint8_t header, spinel_status_t lastStatus) +{ + if (SPINEL_HEADER_GET_IID(header) == 0) + { + mLastStatus = lastStatus; + } + + SendPropteryUpdate(header, SPINEL_CMD_PROP_VALUE_IS, SPINEL_PROP_LAST_STATUS, "i", lastStatus); +} + +void NcpBase::SendPropteryUpdate(uint8_t header, uint8_t command, spinel_prop_key_t key, const char *pack_format, ...) +{ + ThreadError errorCode; + va_list args; + + errorCode = OutboundFrameBegin(); + + if (errorCode == kThreadError_None) + { + errorCode = OutboundFrameFeedPacked("Cii", header, command, key); + } + + if (errorCode == kThreadError_None) + { + va_start(args, pack_format); + errorCode = OutboundFrameFeedVPacked(pack_format, args); + va_end(args); + } + + if (errorCode == kThreadError_None) + { + errorCode = OutboundFrameSend(); + } +} + +void NcpBase::SendPropteryUpdate(uint8_t header, uint8_t command, spinel_prop_key_t key, const uint8_t *value_ptr, + uint16_t value_len) +{ + ThreadError errorCode; + + errorCode = OutboundFrameBegin(); + + if (errorCode == kThreadError_None) + { + errorCode = OutboundFrameFeedPacked("Cii", header, command, key); + } + + if (errorCode == kThreadError_None) + { + errorCode = OutboundFrameFeedData(value_ptr, value_len); + } + + if (errorCode == kThreadError_None) + { + errorCode = OutboundFrameSend(); + } +} + +void NcpBase::SendPropteryUpdate(uint8_t header, uint8_t command, spinel_prop_key_t key, Message &message) +{ + ThreadError errorCode; + + errorCode = OutboundFrameBegin(); + + if (errorCode == kThreadError_None) + { + errorCode = OutboundFrameFeedPacked("Cii", header, command, key); + } + + if (errorCode == kThreadError_None) + { + errorCode = OutboundFrameFeedMessage(message); + } + + if (errorCode == kThreadError_None) + { + errorCode = OutboundFrameSend(); + } +} + +ThreadError +NcpBase::OutboundFrameFeedVPacked(const char *pack_format, va_list args) +{ + uint8_t buf[64]; + ThreadError errorCode = kThreadError_NoBufs; + spinel_ssize_t packed_len; + + packed_len = spinel_datatype_vpack(buf, sizeof(buf), pack_format, args); + + if ((packed_len > 0) && (packed_len <= sizeof(buf))) + { + errorCode = OutboundFrameFeedData(buf, packed_len); + } + + return errorCode; +} + +ThreadError +NcpBase::OutboundFrameFeedPacked(const char *pack_format, ...) +{ + ThreadError errorCode; + va_list args; + + va_start(args, pack_format); + errorCode = OutboundFrameFeedVPacked(pack_format, args); + va_end(args); + + return errorCode; +} + +void NcpBase::HandleSendDone(void *context) +{ + NcpBase *obj = reinterpret_cast(context); + obj->HandleSendDone(); +} + +void NcpBase::HandleSendDone() +{ + if (mSendQueue.GetHead() != NULL) + { + Message &message(*mSendQueue.GetHead()); + HandleReceivedDatagram(message); + mSendQueue.Dequeue(message); + } + + if (mQueuedGetHeader != 0) + { + HandleCommandPropertyGet(mQueuedGetHeader, mQueuedGetKey); + mQueuedGetHeader = 0; + } +} + +} // namespace Thread diff --git a/src/ncp/ncp_base.hpp b/src/ncp/ncp_base.hpp new file mode 100644 index 000000000..01e31b1a3 --- /dev/null +++ b/src/ncp/ncp_base.hpp @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2016, Nest Labs, Inc. + * 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. + */ + +/** + * @file + * This file contains definitions a spinel interface to the OpenThread stack. + */ + +#ifndef NCP_BASE_HPP_ +#define NCP_BASE_HPP_ + +#include +#include +#include + +#include "spinel.h" + +namespace Thread { + +class NcpBase +{ +public: + NcpBase(); + + virtual ThreadError Start(); + virtual ThreadError Stop(); + + virtual ThreadError OutboundFrameBegin(void) = 0; + virtual uint16_t OutboundFrameGetRemaining(void) = 0; + virtual ThreadError OutboundFrameFeedData(const uint8_t *frame, uint16_t frameLength) = 0; + virtual ThreadError OutboundFrameFeedMessage(Message &message) = 0; + virtual ThreadError OutboundFrameFeedPacked(const char *pack_format, ...); + virtual ThreadError OutboundFrameFeedVPacked(const char *pack_format, va_list args); + virtual ThreadError OutboundFrameSend(void) = 0; + + static void HandleReceivedDatagram(void *context, Message &message); + void HandleReceivedDatagram(Message &message); + +protected: + static void HandleReceive(void *context, const uint8_t *buf, uint16_t bufLength); + void HandleReceive(const uint8_t *buf, uint16_t bufLength); + + static void HandleSendDone(void *context); + void HandleSendDone(); + + bool mSending; + +private: + void HandleCommand(uint8_t header, unsigned int command, const uint8_t *arg_ptr, uint16_t arg_len); + void HandleCommandReset(uint8_t header); + void HandleCommandNoop(uint8_t header); + void HandleCommandPropertyGet(uint8_t header, spinel_prop_key_t key); + void HandleCommandPropertySet(uint8_t header, spinel_prop_key_t key, const uint8_t *value_ptr, uint16_t value_len); + void HandleCommandPropertyInsert(uint8_t header, spinel_prop_key_t key, const uint8_t *value_ptr, uint16_t value_len); + void HandleCommandPropertyRemove(uint8_t header, spinel_prop_key_t key, const uint8_t *value_ptr, uint16_t value_len); + + void HandleCommandPropertyGetAddressList(uint8_t header); + void HandleCommandPropertyGetRoutingTable(uint8_t header); + + void SendLastStatus(uint8_t header, spinel_status_t lastStatus); + + void SendPropteryUpdate(uint8_t header, uint8_t command, spinel_prop_key_t key, const uint8_t *value_ptr, + uint16_t value_len); + void SendPropteryUpdate(uint8_t header, uint8_t command, spinel_prop_key_t key, Message &message); + void SendPropteryUpdate(uint8_t header, uint8_t command, spinel_prop_key_t key, const char *format, ...); + + + static void HandleActiveScanResult_Jump(otActiveScanResult *result); + void HandleActiveScanResult(otActiveScanResult *result); + static void HandleUnicastAddressesChanged(void *context); + + static void RunUpdateAddressesTask(void *context); + + Ip6::NetifHandler mNetifHandler; + + spinel_status_t mLastStatus; + uint32_t mChannelMask; + + uint8_t mQueuedGetHeader; + spinel_prop_key_t mQueuedGetKey; + + void RunUpdateAddressesTask(); + Tasklet mUpdateAddressesTask; + + void RunSendScanResultsTask(); + +protected: + MessageQueue mSendQueue; +}; + +} // namespace Thread + +#endif // NCP_BASE_HPP_ diff --git a/src/ncp/spinel.c b/src/ncp/spinel.c new file mode 100644 index 000000000..c349966cb --- /dev/null +++ b/src/ncp/spinel.c @@ -0,0 +1,1123 @@ +/* + * Copyright (c) 2016, Nest Labs, Inc. + * 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. + */ + +/* + * ------------------------------------------------------------------- + * + * ## Unit Test ## + * + * This file includes its own unit test. To compile the unit test, + * simply compile this file with the macro SPINEL_SELF_TEST set to 1. + * For example: + * + * cc spinel.c -Wall -DSPINEL_SELF_TEST=1 -o spinel + * + * ------------------------------------------------------------------- + */ + +// ---------------------------------------------------------------------------- +// MARK: - +// MARK: Headers + +#include "spinel.h" + +#include +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- +// MARK: - + +#ifndef assert_printf +#define assert_printf(fmt, ...) \ +fprintf(stderr, \ + __FILE__ ":%d: " fmt "\n", \ + __LINE__, \ + __VA_ARGS__) +#endif + +#ifndef require_action +#define require_action(c, l, a) \ + do { if (!(c)) { \ + assert_printf("Requirement Failed (%s)", # c); \ + a; \ + goto l; \ + } } while (0) +#endif + +#ifndef require +#define require(c, l) require_action(c, l, {}) +#endif + +// ---------------------------------------------------------------------------- +// MARK: - + +spinel_ssize_t +spinel_packed_uint_decode(const uint8_t *bytes, spinel_size_t len, unsigned int *value_ptr) +{ + spinel_ssize_t ret = 0; + unsigned int value = 0; + + int i = 0; + + do + { + if (len < sizeof(uint8_t)) + { + ret = -1; + break; + } + + value |= ((bytes[0] & 0x7F) << i); + i += 7; + ret += sizeof(uint8_t); + bytes += sizeof(uint8_t); + len -= sizeof(uint8_t); + } + while ((bytes[-1] & 0x80) == 0x80); + + if ((ret > 0) && (value_ptr != NULL)) + { + *value_ptr = value; + } + + return ret; +} + +spinel_ssize_t +spinel_packed_uint_size(unsigned int value) +{ + spinel_size_t ret; + + if (value < (1 << 7)) + { + ret = 1; + } + else if (value < (1 << 14)) + { + ret = 2; + } + else if (value < (1 << 21)) + { + ret = 3; + } + else if (value < (1 << 28)) + { + ret = 4; + } + else + { + ret = 5; + } + + return ret; +} + +spinel_ssize_t +spinel_packed_uint_encode(uint8_t *bytes, spinel_size_t len, unsigned int value) +{ + const spinel_ssize_t encoded_size = spinel_packed_uint_size(value); + + if (len >= encoded_size) + { + spinel_size_t i; + + for (i = 0; i != encoded_size - 1; ++i) + { + *bytes++ = (value & 0x7F) | 0x80; + value = (value >> 7); + } + + *bytes++ = (value & 0x7F); + } + + return encoded_size; +} + +const char * +spinel_next_packed_datatype(const char *pack_format) +{ + int depth = 0; + + do + { + switch (*++pack_format) + { + case '(': + depth++; + break; + + case ')': + depth--; + + if (depth == 0) + { + pack_format++; + } + + break; + } + } + while ((depth > 0) && *pack_format != 0); + + return pack_format; +} + +spinel_ssize_t +spinel_datatype_unpack(const uint8_t *data_ptr, spinel_size_t data_len, const char *pack_format, ...) +{ + spinel_ssize_t ret; + va_list args; + va_start(args, pack_format); + + ret = spinel_datatype_vunpack(data_ptr, data_len, pack_format, args); + + va_end(args); + return ret; +} + +spinel_ssize_t +spinel_datatype_vunpack(const uint8_t *data_ptr, spinel_size_t data_len, const char *pack_format, va_list args) +{ + spinel_ssize_t ret = 0; + + for (; *pack_format != 0; pack_format = spinel_next_packed_datatype(pack_format)) + { + if (*pack_format == ')') + { + // Don't go past the end of a struct. + break; + } + + switch ((spinel_datatype_t)pack_format[0]) + { + case SPINEL_DATATYPE_BOOL_C: + { + bool *arg_ptr = va_arg(args, bool *); + require_action(data_len >= sizeof(uint8_t), bail, (ret = -1, errno = EOVERFLOW)); + + if (arg_ptr) + { + *arg_ptr = data_ptr[0]; + } + + ret += sizeof(uint8_t); + data_ptr += sizeof(uint8_t); + data_len -= sizeof(uint8_t); + break; + } + + case SPINEL_DATATYPE_INT8_C: + case SPINEL_DATATYPE_UINT8_C: + { + uint8_t *arg_ptr = va_arg(args, uint8_t *); + require_action(data_len >= sizeof(uint8_t), bail, (ret = -1, errno = EOVERFLOW)); + + if (arg_ptr) + { + *arg_ptr = data_ptr[0]; + } + + ret += sizeof(uint8_t); + data_ptr += sizeof(uint8_t); + data_len -= sizeof(uint8_t); + break; + } + + case SPINEL_DATATYPE_INT16_C: + case SPINEL_DATATYPE_UINT16_C: + { + uint16_t *arg_ptr = va_arg(args, uint16_t *); + require_action(data_len >= sizeof(uint16_t), bail, (ret = -1, errno = EOVERFLOW)); + + if (arg_ptr) + { + *arg_ptr = ((data_ptr[1] << 8) | data_ptr[0]); + } + + ret += sizeof(uint16_t); + data_ptr += sizeof(uint16_t); + data_len -= sizeof(uint16_t); + break; + } + + case SPINEL_DATATYPE_INT32_C: + case SPINEL_DATATYPE_UINT32_C: + { + uint32_t *arg_ptr = va_arg(args, uint32_t *); + require_action(data_len >= sizeof(uint32_t), bail, (ret = -1, errno = EOVERFLOW)); + + if (arg_ptr) + { + *arg_ptr = ((data_ptr[3] << 24) | (data_ptr[2] << 16) | (data_ptr[1] << 8) | data_ptr[0]); + } + + ret += sizeof(uint32_t); + data_ptr += sizeof(uint32_t); + data_len -= sizeof(uint32_t); + break; + } + + case SPINEL_DATATYPE_IPv6ADDR_C: + { + spinel_ipv6addr_t **arg_ptr = va_arg(args, spinel_ipv6addr_t **); + require_action(data_len >= sizeof(spinel_ipv6addr_t), bail, (ret = -1, errno = EOVERFLOW)); + + if (arg_ptr) + { + *arg_ptr = (spinel_ipv6addr_t *)data_ptr; + } + + ret += sizeof(spinel_ipv6addr_t); + data_ptr += sizeof(spinel_ipv6addr_t); + data_len -= sizeof(spinel_ipv6addr_t); + break; + } + + case SPINEL_DATATYPE_EUI64_C: + { + spinel_eui64_t **arg_ptr = va_arg(args, spinel_eui64_t **); + require_action(data_len >= sizeof(spinel_eui64_t), bail, (ret = -1, errno = EOVERFLOW)); + + if (arg_ptr) + { + *arg_ptr = (spinel_eui64_t *)data_ptr; + } + + ret += sizeof(spinel_eui64_t); + data_ptr += sizeof(spinel_eui64_t); + data_len -= sizeof(spinel_eui64_t); + break; + } + + case SPINEL_DATATYPE_EUI48_C: + { + spinel_eui48_t **arg_ptr = va_arg(args, spinel_eui48_t **); + require_action(data_len >= sizeof(spinel_eui48_t), bail, (ret = -1, errno = EOVERFLOW)); + + if (arg_ptr) + { + *arg_ptr = (spinel_eui48_t *)data_ptr; + } + + ret += sizeof(spinel_eui48_t); + data_ptr += sizeof(spinel_eui48_t); + data_len -= sizeof(spinel_eui48_t); + break; + } + + case SPINEL_DATATYPE_UINT_PACKED_C: + { + uint32_t *arg_ptr = va_arg(args, uint32_t *); + spinel_ssize_t pui_len = spinel_packed_uint_decode(data_ptr, data_len, arg_ptr); + + require(pui_len > 0, bail); + + require(pui_len <= data_len, bail); + + ret += pui_len; + data_ptr += pui_len; + data_len -= pui_len; + break; + } + + case SPINEL_DATATYPE_UTF8_C: + { + const char **arg_ptr = va_arg(args, const char **); + ssize_t len = strnlen((const char *)data_ptr, data_len) + 1; + require_action((len <= data_len) || (data_ptr[data_len - 1] != 0), bail, (ret = -1, errno = EOVERFLOW)); + + if (arg_ptr) + { + *arg_ptr = (const char *)data_ptr; + } + + ret += len; + data_ptr += len; + data_len -= len; + break; + } + + case SPINEL_DATATYPE_DATA_C: + { + spinel_ssize_t pui_len = 0; + uint16_t block_len = 0; + const uint8_t *block_ptr = data_ptr; + const uint8_t **block_ptr_ptr = va_arg(args, const uint8_t **); + unsigned int *block_len_ptr = va_arg(args, unsigned int *); + char nextformat = *spinel_next_packed_datatype(pack_format); + + if ((nextformat != 0) && (nextformat != ')')) + { + pui_len = spinel_datatype_unpack(data_ptr, data_len, SPINEL_DATATYPE_UINT16_S, &block_len); + //pui_len = spinel_packed_uint_decode(data_ptr, data_len, &block_len); + block_ptr += pui_len; + + require(pui_len > 0, bail); + require(block_len < SPINEL_FRAME_MAX_SIZE, bail); + } + else + { + block_len = data_len; + pui_len = 0; + } + + require_action(data_len >= (block_len + pui_len), bail, (ret = -1, errno = EOVERFLOW)); + + if (NULL != block_ptr_ptr) + { + *block_ptr_ptr = block_ptr; + } + + if (NULL != block_len_ptr) + { + *block_len_ptr = block_len; + } + + block_len += pui_len; + ret += block_len; + data_ptr += block_len; + data_len -= block_len; + break; + } + + case SPINEL_DATATYPE_STRUCT_C: + { + spinel_ssize_t pui_len = 0; + uint16_t block_len = 0; + unsigned int actual_len = 0; + const uint8_t *block_ptr = data_ptr; + char nextformat = *spinel_next_packed_datatype(pack_format); + + if ((nextformat != 0) && (nextformat != ')')) + { + pui_len = spinel_datatype_unpack(data_ptr, data_len, SPINEL_DATATYPE_UINT16_S, &block_len); + block_ptr += pui_len; + + require(pui_len > 0, bail); + require(block_len < SPINEL_FRAME_MAX_SIZE, bail); + } + else + { + block_len = data_len; + pui_len = 0; + } + + require_action(data_len >= (block_len + pui_len), bail, (ret = -1, errno = EOVERFLOW)); + + actual_len = spinel_datatype_vunpack(block_ptr, block_len, pack_format + 2, args); + + require_action((int)actual_len > -1, bail, (ret = -1, errno = EOVERFLOW)); + + if (pui_len) + { + block_len += pui_len; + } + else + { + block_len = actual_len; + } + + ret += block_len; + data_ptr += block_len; + data_len -= block_len; + break; + } + + case '.': + // Skip. + break; + + case SPINEL_DATATYPE_ARRAY_C: + default: + // Unsupported Type! + ret = -1; + errno = EINVAL; + goto bail; + } + } + + return ret; + +bail: + return ret; +} + +spinel_ssize_t +spinel_datatype_pack(uint8_t *data_ptr, spinel_size_t data_len_max, const char *pack_format, ...) +{ + int ret; + va_list args; + va_start(args, pack_format); + + ret = spinel_datatype_vpack(data_ptr, data_len_max, pack_format, args); + + va_end(args); + return ret; +} + +spinel_ssize_t +spinel_datatype_vpack(uint8_t *data_ptr, spinel_size_t data_len_max, const char *pack_format, va_list args) +{ + spinel_ssize_t ret = 0; + + for (; *pack_format != 0; pack_format = spinel_next_packed_datatype(pack_format)) + { + if (*pack_format == ')') + { + // Don't go past the end of a struct. + break; + } + + switch ((spinel_datatype_t)*pack_format) + { + case SPINEL_DATATYPE_BOOL_C: + { + bool arg = va_arg(args, int); + ret += sizeof(uint8_t); + + if (data_len_max >= sizeof(uint8_t)) + { + data_ptr[0] = (arg != false); + data_ptr += sizeof(uint8_t); + data_len_max -= sizeof(uint8_t); + } + else + { + data_len_max = 0; + } + + break; + } + + case SPINEL_DATATYPE_INT8_C: + case SPINEL_DATATYPE_UINT8_C: + { + uint8_t arg = va_arg(args, int); + ret += sizeof(uint8_t); + + if (data_len_max >= sizeof(uint8_t)) + { + data_ptr[0] = arg; + data_ptr += sizeof(uint8_t); + data_len_max -= sizeof(uint8_t); + } + else + { + data_len_max = 0; + } + + break; + } + + case SPINEL_DATATYPE_INT16_C: + case SPINEL_DATATYPE_UINT16_C: + { + uint16_t arg = va_arg(args, int); + ret += sizeof(uint16_t); + + if (data_len_max >= sizeof(uint16_t)) + { + data_ptr[1] = (arg >> 8); + data_ptr[0] = (arg >> 0); + data_ptr += sizeof(uint16_t); + data_len_max -= sizeof(uint16_t); + } + else + { + data_len_max = 0; + } + + break; + } + + case SPINEL_DATATYPE_INT32_C: + case SPINEL_DATATYPE_UINT32_C: + { + uint32_t arg = va_arg(args, int); + ret += sizeof(uint32_t); + + if (data_len_max >= sizeof(uint32_t)) + { + data_ptr[3] = (arg >> 24); + data_ptr[2] = (arg >> 16); + data_ptr[1] = (arg >> 8); + data_ptr[0] = (arg >> 0); + data_ptr += sizeof(uint32_t); + data_len_max -= sizeof(uint32_t); + } + else + { + data_len_max = 0; + } + + break; + } + + case SPINEL_DATATYPE_IPv6ADDR_C: + { + spinel_ipv6addr_t *arg = va_arg(args, spinel_ipv6addr_t *); + ret += sizeof(spinel_ipv6addr_t); + + if (data_len_max >= sizeof(spinel_ipv6addr_t)) + { + *(spinel_ipv6addr_t *)data_ptr = *arg; + data_ptr += sizeof(spinel_ipv6addr_t); + data_len_max -= sizeof(spinel_ipv6addr_t); + } + else + { + data_len_max = 0; + } + + break; + } + + case SPINEL_DATATYPE_EUI48_C: + { + spinel_eui48_t *arg = va_arg(args, spinel_eui48_t *); + ret += sizeof(spinel_eui48_t); + + if (data_len_max >= sizeof(spinel_eui48_t)) + { + *(spinel_eui48_t *)data_ptr = *arg; + data_ptr += sizeof(spinel_eui48_t); + data_len_max -= sizeof(spinel_eui48_t); + } + else + { + data_len_max = 0; + } + + break; + } + + case SPINEL_DATATYPE_EUI64_C: + { + spinel_eui64_t *arg = va_arg(args, spinel_eui64_t *); + ret += sizeof(spinel_eui64_t); + + if (data_len_max >= sizeof(spinel_eui64_t)) + { + *(spinel_eui64_t *)data_ptr = *arg; + data_ptr += sizeof(spinel_eui64_t); + data_len_max -= sizeof(spinel_eui64_t); + } + else + { + data_len_max = 0; + } + + break; + } + + case SPINEL_DATATYPE_UINT_PACKED_C: + { + uint32_t arg = va_arg(args, uint32_t); + spinel_ssize_t encoded_size = spinel_packed_uint_encode(data_ptr, data_len_max, arg); + ret += encoded_size; + + if (data_len_max >= encoded_size) + { + data_ptr += encoded_size; + data_len_max -= encoded_size; + } + else + { + data_len_max = 0; + } + + break; + } + + case SPINEL_DATATYPE_UTF8_C: + { + const char *string_arg = va_arg(args, const char *); + size_t string_arg_len = 0; + + if (string_arg) + { + string_arg_len = strlen(string_arg) + 1; + } + else + { + string_arg = ""; + string_arg_len = 1; + } + + ret += string_arg_len; + + if (data_len_max >= string_arg_len) + { + memcpy(data_ptr, string_arg, string_arg_len); + + data_ptr += string_arg_len; + data_len_max -= string_arg_len; + } + else + { + data_len_max = 0; + } + + break; + } + + case SPINEL_DATATYPE_DATA_C: + { + uint8_t *arg = va_arg(args, uint8_t *); + uint32_t data_size_arg = va_arg(args, uint32_t); + spinel_ssize_t size_len = 0; + char nextformat = *spinel_next_packed_datatype(pack_format); + + if (nextformat != 0 && nextformat != ')') + { + size_len = spinel_datatype_pack(data_ptr, data_len_max, SPINEL_DATATYPE_UINT16_S, data_size_arg); + require_action(size_len > 0, bail, {ret = -1; errno = EINVAL;}); + } + + ret += size_len + data_size_arg; + + if (data_len_max >= size_len + data_size_arg) + { + data_ptr += size_len; + data_len_max -= size_len; + + memcpy(data_ptr, arg, data_size_arg); + + data_ptr += data_size_arg; + data_len_max -= data_size_arg; + } + else + { + data_len_max = 0; + } + + break; + } + + case SPINEL_DATATYPE_STRUCT_C: + { + spinel_ssize_t struct_len = 0; + spinel_ssize_t size_len = 0; + char nextformat = *spinel_next_packed_datatype(pack_format); + + require_action(pack_format[1] == '(', bail, {ret = -1; errno = EINVAL;}); + + // First we figure out the size of the struct + { + va_list subargs; + va_copy(subargs, args); + struct_len = spinel_datatype_vpack(NULL, 0, pack_format + 2, subargs); + va_end(subargs); + } + + if (nextformat != 0 && nextformat != ')') + { + size_len = spinel_datatype_pack(data_ptr, data_len_max, SPINEL_DATATYPE_UINT16_S, struct_len); + require_action(size_len > 0, bail, {ret = -1; errno = EINVAL;}); + } + + ret += size_len + struct_len; + + if (struct_len + size_len <= data_len_max) + { + data_ptr += size_len; + data_len_max -= size_len; + + struct_len = spinel_datatype_vpack(data_ptr, data_len_max, pack_format + 2, args); + + data_ptr += struct_len; + data_len_max -= struct_len; + } + else + { + data_len_max = 0; + } + + break; + } + + case '.': + // Skip. + break; + + default: + // Unsupported Type! + ret = -1; + errno = EINVAL; + goto bail; + + } + } + +bail: + return ret; +} + +// ---------------------------------------------------------------------------- +// MARK: - + +const char * +spinel_prop_key_to_cstr(spinel_prop_key_t prop_key) +{ + const char *ret = "UNKNOWN"; + + switch (prop_key) + { + case SPINEL_PROP_LAST_STATUS: + ret = "PROP_LAST_STATUS"; + break; + + case SPINEL_PROP_PROTOCOL_VERSION: + ret = "PROP_PROTOCOL_VERSION"; + break; + + case SPINEL_PROP_CAPABILITIES: + ret = "PROP_CAPABILITIES"; + break; + + case SPINEL_PROP_NCP_VERSION: + ret = "PROP_NCP_VERSION"; + break; + + case SPINEL_PROP_INTERFACE_COUNT: + ret = "PROP_INTERFACE_COUNT"; + break; + + case SPINEL_PROP_POWER_STATE: + ret = "PROP_POWER_STATE"; + break; + + case SPINEL_PROP_HWADDR: + ret = "PROP_HWADDR"; + break; + + case SPINEL_PROP_LOCK: + ret = "PROP_LOCK"; + break; + + case SPINEL_PROP_HBO_MEM_MAX: + ret = "PROP_HBO_MEM_MAX"; + break; + + case SPINEL_PROP_HBO_BLOCK_MAX: + ret = "PROP_HBO_BLOCK_MAX"; + break; + + case SPINEL_PROP_STREAM_DEBUG: + ret = "PROP_STREAM_DEBUG"; + break; + + case SPINEL_PROP_STREAM_RAW: + ret = "PROP_STREAM_RAW"; + break; + + case SPINEL_PROP_STREAM_NET: + ret = "PROP_STREAM_NET"; + break; + + case SPINEL_PROP_STREAM_NET_INSECURE: + ret = "PROP_STREAM_NET_INSECURE"; + break; + + case SPINEL_PROP_PHY_ENABLED: + ret = "PROP_PHY_ENABLED"; + break; + + case SPINEL_PROP_PHY_CHAN: + ret = "PROP_PHY_CHAN"; + break; + + case SPINEL_PROP_PHY_CHAN_SUPPORTED: + ret = "PROP_PHY_CHAN_SUPPORTED"; + break; + + case SPINEL_PROP_PHY_FREQ: + ret = "PROP_PHY_FREQ"; + break; + + case SPINEL_PROP_PHY_CCA_THRESHOLD: + ret = "PROP_PHY_CCA_THRESHOLD"; + break; + + case SPINEL_PROP_PHY_TX_POWER: + ret = "PROP_PHY_TX_POWER"; + break; + + case SPINEL_PROP_PHY_RSSI: + ret = "PROP_PHY_RSSI"; + break; + + case SPINEL_PROP_PHY_RAW_STREAM_ENABLED: + ret = "PROP_PHY_RAW_STREAM_ENABLED"; + break; + + case SPINEL_PROP_MAC_SCAN_STATE: + ret = "PROP_MAC_SCAN_STATE"; + break; + + case SPINEL_PROP_MAC_SCAN_MASK: + ret = "PROP_MAC_SCAN_MASK"; + break; + + case SPINEL_PROP_MAC_SCAN_BEACON: + ret = "PROP_MAC_SCAN_BEACON"; + break; + + case SPINEL_PROP_MAC_15_4_LADDR: + ret = "PROP_MAC_15_4_LADDR"; + break; + + case SPINEL_PROP_MAC_15_4_SADDR: + ret = "PROP_MAC_15_4_SADDR"; + break; + + case SPINEL_PROP_MAC_15_4_PANID: + ret = "PROP_MAC_15_4_PANID"; + break; + + case SPINEL_PROP_NET_SAVED: + ret = "PROP_NET_SAVED"; + break; + + case SPINEL_PROP_NET_ENABLED: + ret = "PROP_NET_ENABLED"; + break; + + case SPINEL_PROP_NET_STATE: + ret = "PROP_NET_STATE"; + break; + + case SPINEL_PROP_NET_ROLE: + ret = "PROP_NET_ROLE"; + break; + + case SPINEL_PROP_NET_NETWORK_NAME: + ret = "PROP_NET_NETWORK_NAME"; + break; + + case SPINEL_PROP_NET_XPANID: + ret = "PROP_NET_XPANID"; + break; + + case SPINEL_PROP_NET_MASTER_KEY: + ret = "PROP_NET_MASTER_KEY"; + break; + + case SPINEL_PROP_NET_KEY_SEQUENCE: + ret = "PROP_NET_KEY_SEQUENCE"; + break; + + case SPINEL_PROP_NET_PARTITION_ID: + ret = "PROP_NET_PARTITION_ID"; + break; + + case SPINEL_PROP_THREAD_LEADER: + ret = "PROP_THREAD_LEADER"; + break; + + case SPINEL_PROP_THREAD_PARENT: + ret = "PROP_THREAD_PARENT"; + break; + + case SPINEL_PROP_THREAD_CHILD_TABLE: + ret = "PROP_THREAD_CHILD_TABLE"; + break; + + case SPINEL_PROP_IPV6_LL_ADDR: + ret = "PROP_IPV6_LL_ADDR"; + break; + + case SPINEL_PROP_IPV6_ML_ADDR: + ret = "PROP_IPV6_ML_ADDR"; + break; + + case SPINEL_PROP_IPV6_ML_PREFIX: + ret = "PROP_IPV6_ML_PREFIX"; + break; + + case SPINEL_PROP_IPV6_ADDRESS_TABLE: + ret = "PROP_IPV6_ADDRESS_TABLE"; + break; + + case SPINEL_PROP_IPV6_ROUTE_TABLE: + ret = "PROP_IPV6_ROUTE_TABLE"; + break; + + default: + break; + } + + return ret; +} + + +/* -------------------------------------------------------------------------- */ + +#if SPINEL_SELF_TEST + +#include +#include + + +int +main(void) +{ + int ret = -1; + + const char static_string[] = "static_string"; + uint8_t buffer[1024]; + ssize_t len; + + len = spinel_datatype_pack(buffer, sizeof(buffer), "CiiLU", 0x88, 9, 0xA3, 0xDEADBEEF, static_string); + + if (len != 22) + { + printf("error:%d: len != 22; (%d)\n", __LINE__, (int)len); + goto bail; + } + + { + uint8_t c = 0; + unsigned int i1 = 0; + unsigned int i2 = 0; + uint32_t l = 0; + const char *str = NULL; + + len = spinel_datatype_unpack(buffer, (spinel_size_t)len, "CiiLU", &c, &i1, &i2, &l, &str); + + if (len != 22) + { + printf("error:%d: len != 22; (%d)\n", __LINE__, (int)len); + goto bail; + } + + if (c != 0x88) + { + printf("error: x != 0x88; (%d)\n", c); + goto bail; + } + + if (i1 != 9) + { + printf("error: i1 != 9; (%d)\n", i1); + goto bail; + } + + if (i2 != 0xA3) + { + printf("error: i2 != 0xA3; (0x%02X)\n", i2); + goto bail; + } + + if (l != 0xDEADBEEF) + { + printf("error: l != 0xDEADBEEF; (0x%08X)\n", l); + goto bail; + } + + if (strcmp(str, static_string) != 0) + { + printf("error:%d: strcmp(str,static_string) != 0\n", __LINE__); + goto bail; + } + } + + // ----------------------------------- + + memset(buffer, 0xAA, sizeof(buffer)); + + len = spinel_datatype_pack(buffer, sizeof(buffer), "CiT(iL)U", 0x88, 9, 0xA3, 0xDEADBEEF, static_string); + + if (len != 24) + { + printf("error:%d: len != 24; (%d)\n", __LINE__, (int)len); + goto bail; + } + + + + { + uint8_t c = 0; + unsigned int i1 = 0; + unsigned int i2 = 0; + uint32_t l = 0; + const char *str = NULL; + + len = spinel_datatype_unpack(buffer, (spinel_size_t)len, "CiT(iL)U", &c, &i1, &i2, &l, &str); + + if (len != 24) + { + printf("error:%d: len != 24; (%d)\n", __LINE__, (int)len); + goto bail; + } + + if (c != 0x88) + { + printf("error: x != 0x88; (%d)\n", c); + goto bail; + } + + if (i1 != 9) + { + printf("error: i1 != 9; (%d)\n", i1); + goto bail; + } + + if (i2 != 0xA3) + { + printf("error: i2 != 0xA3; (0x%02X)\n", i2); + goto bail; + } + + if (l != 0xDEADBEEF) + { + printf("error: l != 0xDEADBEEF; (0x%08X)\n", l); + goto bail; + } + + if (strcmp(str, static_string) != 0) + { + printf("error:%d: strcmp(str,static_string) != 0\n", __LINE__); + goto bail; + } + } + + + + printf("OK\n"); + ret = 0; + return ret; + +bail: + printf("FAILURE\n"); + return ret; +} + +#endif // #if SPINEL_SELF_TEST diff --git a/src/ncp/spinel.h b/src/ncp/spinel.h new file mode 100644 index 000000000..566bbb28a --- /dev/null +++ b/src/ncp/spinel.h @@ -0,0 +1,415 @@ +/* + * Copyright (c) 2016, Nest Labs, Inc. + * 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 SPINEL_HEADER_INCLUDED +#define SPINEL_HEADER_INCLUDED 1 + +#include +#include +#include +#include +#include + +__BEGIN_DECLS + +// ---------------------------------------------------------------------------- + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +# if defined(__GNUC__) && !SPINEL_EMBEDDED +# define SPINEL_API_EXTERN extern __attribute__ ((visibility ("default"))) +# define SPINEL_API_NONNULL_ALL __attribute__((nonnull)) +# define SPINEL_API_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +# endif // ifdef __GNUC__ +#endif // ifndef DOXYGEN_SHOULD_SKIP_THIS + +#ifndef SPINEL_API_EXTERN +# define SPINEL_API_EXTERN extern +#endif + +#ifndef SPINEL_API_NONNULL_ALL +# define SPINEL_API_NONNULL_ALL +#endif + +#ifndef SPINEL_API_WARN_UNUSED_RESULT +# define SPINEL_API_WARN_UNUSED_RESULT +#endif + +// ---------------------------------------------------------------------------- + +#define SPINEL_PROTOCOL_VERSION_THREAD_MAJOR 0 +#define SPINEL_PROTOCOL_VERSION_THREAD_MINOR 0 + +#define SPINEL_FRAME_MAX_SIZE 1300 + +// ---------------------------------------------------------------------------- + +typedef enum +{ + SPINEL_STATUS_OK = 0, //!< Operation has completed successfully. + SPINEL_STATUS_FAILURE = 1, //!< Operation has failed for some undefined reason. + + SPINEL_STATUS_UNIMPLEMENTED = 2, + SPINEL_STATUS_INVALID_ARGUMENT = 3, + SPINEL_STATUS_INVALID_STATE = 4, + SPINEL_STATUS_INVALID_COMMAND = 5, + SPINEL_STATUS_INVALID_INTERFACE = 6, + SPINEL_STATUS_INTERNAL_ERROR = 7, + SPINEL_STATUS_SECURITY_ERROR = 8, + SPINEL_STATUS_PARSE_ERROR = 9, + SPINEL_STATUS_IN_PROGRESS = 10, + SPINEL_STATUS_NOMEM = 11, + SPINEL_STATUS_BUSY = 12, + SPINEL_STATUS_PROPERTY_NOT_FOUND = 12, + SPINEL_STATUS_DROPPED = 14, + SPINEL_STATUS_EMPTY = 15, + + SPINEL_STATUS_RESET__BEGIN = 112, + SPINEL_STATUS_RESET_POWER_ON = SPINEL_STATUS_RESET__BEGIN + 0, + SPINEL_STATUS_RESET_EXTERNAL = SPINEL_STATUS_RESET__BEGIN + 1, + SPINEL_STATUS_RESET_SOFTWARE = SPINEL_STATUS_RESET__BEGIN + 2, + SPINEL_STATUS_RESET_FAULT = SPINEL_STATUS_RESET__BEGIN + 3, + SPINEL_STATUS_RESET_CRASH = SPINEL_STATUS_RESET__BEGIN + 4, + SPINEL_STATUS_RESET_ASSERT = SPINEL_STATUS_RESET__BEGIN + 5, + SPINEL_STATUS_RESET_OTHER = SPINEL_STATUS_RESET__BEGIN + 6, + SPINEL_STATUS_RESET__END = 128, + + + SPINEL_STATUS_VENDOR__BEGIN = 15360, + SPINEL_STATUS_VENDOR__END = 16384, + + SPINEL_STATUS_EXPERIMENTAL__BEGIN = 2000000, + SPINEL_STATUS_EXPERIMENTAL__END = 2097152, +} spinel_status_t; + +typedef enum +{ + SPINEL_NET_STATE_OFFLINE = 0, + SPINEL_NET_STATE_DETACHED = 1, + SPINEL_NET_STATE_ATTACHING = 2, + SPINEL_NET_STATE_ATTACHED = 3, +} spinel_net_state_t; + +typedef enum +{ + SPINEL_NET_ROLE_NONE = 0, + SPINEL_NET_ROLE_CHILD = 1, + SPINEL_NET_ROLE_ROUTER = 2, + SPINEL_NET_ROLE_LEADER = 3, +} spinel_net_role_t; + +typedef enum +{ + SPINEL_SCAN_STATE_IDLE = 0, + SPINEL_SCAN_STATE_BEACON = 1, + SPINEL_SCAN_STATE_ENERGY = 2, +} spinel_scan_state_t; + +typedef enum +{ + SPINEL_POWER_STATE_OFFLINE = 0, + SPINEL_POWER_STATE_DEEP_SLEEP = 1, + SPINEL_POWER_STATE_STANDBY = 2, + SPINEL_POWER_STATE_LOW_POWER = 3, + SPINEL_POWER_STATE_ONLINE = 4, +} spinel_power_state_t; + +enum +{ + SPINEL_PROTOCOL_TYPE_ZIGBEE = 1, + SPINEL_PROTOCOL_TYPE_ZIGBEE_IP = 2, + SPINEL_PROTOCOL_TYPE_THREAD = 3, +}; + +typedef struct +{ + uint8_t bytes[8]; +} spinel_eui64_t; + +typedef struct +{ + uint8_t bytes[8]; +} spinel_net_xpanid_t; + +typedef struct +{ + uint8_t bytes[6]; +} spinel_eui48_t; + +typedef struct in6_addr spinel_ipv6addr_t; +typedef int spinel_ssize_t; +typedef unsigned int spinel_size_t; +typedef uint8_t spinel_tid_t; + +enum +{ + SPINEL_CMD_NOOP = 0, + SPINEL_CMD_RESET = 1, + SPINEL_CMD_PROP_VALUE_GET = 2, + SPINEL_CMD_PROP_VALUE_SET = 3, + SPINEL_CMD_PROP_VALUE_INSERT = 4, + SPINEL_CMD_PROP_VALUE_REMOVE = 5, + SPINEL_CMD_PROP_VALUE_IS = 6, + SPINEL_CMD_PROP_VALUE_INSERTED = 7, + SPINEL_CMD_PROP_VALUE_REMOVED = 8, + + SPINEL_CMD_NET_SAVE = 9, + SPINEL_CMD_NET_CLEAR = 10, + SPINEL_CMD_NET_RECALL = 11, + + SPINEL_CMD_HBO_OFFLOAD = 12, + SPINEL_CMD_HBO_RECLAIM = 13, + SPINEL_CMD_HBO_DROP = 14, + SPINEL_CMD_HBO_OFFLOADED = 15, + SPINEL_CMD_HBO_RECLAIMED = 16, + SPINEL_CMD_HBO_DROPED = 17, + + SPINEL_CMD_NEST__BEGIN = 15296, + SPINEL_CMD_NEST__END = 15360, + + SPINEL_CMD_VENDOR__BEGIN = 15360, + SPINEL_CMD_VENDOR__END = 16384, + + SPINEL_CMD_EXPERIMENTAL__BEGIN = 2000000, + SPINEL_CMD_EXPERIMENTAL__END = 2097152, +}; + +enum +{ + SPINEL_CAP_LOCK = 1, + SPINEL_CAP_NET_SAVE = 2, + SPINEL_CAP_HBO = 3, + SPINEL_CAP_POWER_SAVE = 4, + + SPINEL_CAP_802_15_4__BEGIN = 16, + SPINEL_CAP_802_15_4_2003 = (SPINEL_CAP_802_15_4__BEGIN + 0), + SPINEL_CAP_802_15_4_2006 = (SPINEL_CAP_802_15_4__BEGIN + 1), + SPINEL_CAP_802_15_4_2011 = (SPINEL_CAP_802_15_4__BEGIN + 2), + SPINEL_CAP_802_15_4_PIB = (SPINEL_CAP_802_15_4__BEGIN + 5), + SPINEL_CAP_802_15_4_2450MHZ_OQPSK = (SPINEL_CAP_802_15_4__BEGIN + 8), + SPINEL_CAP_802_15_4_915MHZ_OQPSK = (SPINEL_CAP_802_15_4__BEGIN + 9), + SPINEL_CAP_802_15_4_868MHZ_OQPSK = (SPINEL_CAP_802_15_4__BEGIN + 10), + SPINEL_CAP_802_15_4_915MHZ_BPSK = (SPINEL_CAP_802_15_4__BEGIN + 11), + SPINEL_CAP_802_15_4_868MHZ_BPSK = (SPINEL_CAP_802_15_4__BEGIN + 12), + SPINEL_CAP_802_15_4_915MHZ_ASK = (SPINEL_CAP_802_15_4__BEGIN + 13), + SPINEL_CAP_802_15_4_868MHZ_ASK = (SPINEL_CAP_802_15_4__BEGIN + 14), + SPINEL_CAP_802_15_4__END = 32, + + SPINEL_CAP_ROLE__BEGIN = 48, + SPINEL_CAP_ROLE_ROUTER = (SPINEL_CAP_ROLE__BEGIN + 0), + SPINEL_CAP_ROLE_SLEEPY = (SPINEL_CAP_ROLE__BEGIN + 1), + SPINEL_CAP_ROLE__END = 52, + + SPINEL_CAP_NET__BEGIN = 52, + SPINEL_CAP_NET_THREAD_1_0 = (SPINEL_CAP_NET__BEGIN + 0), + SPINEL_CAP_NET__END = 64, + + SPINEL_CAP_NEST__BEGIN = 15296, + SPINEL_CAP_NEST_LEGACY_INTERFACE = (SPINEL_CAP_NEST__BEGIN + 0), + SPINEL_CAP_NEST_LEGACY_NET_WAKE = (SPINEL_CAP_NEST__BEGIN + 1), + SPINEL_CAP_NEST_TRANSMIT_HOOK = (SPINEL_CAP_NEST__BEGIN + 2), + SPINEL_CAP_NEST__END = 15360, + + SPINEL_CAP_VENDOR__BEGIN = 15360, + SPINEL_CAP_VENDOR__END = 16384, + + SPINEL_CAP_EXPERIMENTAL__BEGIN = 2000000, + SPINEL_CAP_EXPERIMENTAL__END = 2097152, +}; + +typedef enum +{ + SPINEL_PROP_LAST_STATUS = 0, // status [i] + SPINEL_PROP_PROTOCOL_VERSION = 1, // interface type, major, minor, vendor [i,i,i,i] + SPINEL_PROP_CAPABILITIES = 2, // capability list [A(i)] + SPINEL_PROP_NCP_VERSION = 3, // version string [U] + SPINEL_PROP_INTERFACE_COUNT = 4, // Interface count [C] + SPINEL_PROP_POWER_STATE = 5, // PowerState [C] + SPINEL_PROP_HWADDR = 6, // PermEUI64 [E] + SPINEL_PROP_LOCK = 7, // PropLock [b] + SPINEL_PROP_HBO_MEM_MAX = 8, // Max offload mem [S] + SPINEL_PROP_HBO_BLOCK_MAX = 9, // Max offload block [S] + + SPINEL_PROP_PHY__BEGIN = 0x20, + SPINEL_PROP_PHY_ENABLED = SPINEL_PROP_PHY__BEGIN + 0, // [b] + SPINEL_PROP_PHY_CHAN = SPINEL_PROP_PHY__BEGIN + 1, // [C] + SPINEL_PROP_PHY_CHAN_SUPPORTED = SPINEL_PROP_PHY__BEGIN + 2, // [A(C)] + SPINEL_PROP_PHY_FREQ = SPINEL_PROP_PHY__BEGIN + 3, // kHz [L] + SPINEL_PROP_PHY_CCA_THRESHOLD = SPINEL_PROP_PHY__BEGIN + 4, // dBm [c] + SPINEL_PROP_PHY_TX_POWER = SPINEL_PROP_PHY__BEGIN + 5, // [c] + SPINEL_PROP_PHY_RSSI = SPINEL_PROP_PHY__BEGIN + 6, // dBm [c] + SPINEL_PROP_PHY_RAW_STREAM_ENABLED = SPINEL_PROP_PHY__BEGIN + 7, // [C] + SPINEL_PROP_PHY__END = 0x30, + + SPINEL_PROP_MAC__BEGIN = 0x30, + SPINEL_PROP_MAC_SCAN_STATE = SPINEL_PROP_MAC__BEGIN + 0, // [C] + SPINEL_PROP_MAC_SCAN_MASK = SPINEL_PROP_MAC__BEGIN + 1, // [A(C)] + SPINEL_PROP_MAC_SCAN_PERIOD = SPINEL_PROP_MAC__BEGIN + 2, // ms-per-channel [S] + SPINEL_PROP_MAC_SCAN_BEACON = SPINEL_PROP_MAC__BEGIN + 3, // chan,rssi,(laddr,saddr,panid,lqi),(proto,xtra) [CcT(ESSC)T(i).] + SPINEL_PROP_MAC_15_4_LADDR = SPINEL_PROP_MAC__BEGIN + 4, // [E] + SPINEL_PROP_MAC_15_4_SADDR = SPINEL_PROP_MAC__BEGIN + 5, // [S] + SPINEL_PROP_MAC_15_4_PANID = SPINEL_PROP_MAC__BEGIN + 6, // [S] + SPINEL_PROP_MAC__END = 0x40, + + SPINEL_PROP_NET__BEGIN = 0x40, + SPINEL_PROP_NET_SAVED = SPINEL_PROP_NET__BEGIN + 0, // [b] + SPINEL_PROP_NET_ENABLED = SPINEL_PROP_NET__BEGIN + 1, // [b] + SPINEL_PROP_NET_STATE = SPINEL_PROP_NET__BEGIN + 2, // [C] + SPINEL_PROP_NET_ROLE = SPINEL_PROP_NET__BEGIN + 3, // [C] + SPINEL_PROP_NET_NETWORK_NAME = SPINEL_PROP_NET__BEGIN + 4, // [U] + SPINEL_PROP_NET_XPANID = SPINEL_PROP_NET__BEGIN + 5, // [D] + SPINEL_PROP_NET_MASTER_KEY = SPINEL_PROP_NET__BEGIN + 6, // [D] + SPINEL_PROP_NET_KEY_SEQUENCE = SPINEL_PROP_NET__BEGIN + 7, // [L] + SPINEL_PROP_NET_PARTITION_ID = SPINEL_PROP_NET__BEGIN + 8, // [L] + SPINEL_PROP_NET__END = 0x50, + + SPINEL_PROP_THREAD__BEGIN = 0x50, + SPINEL_PROP_THREAD_LEADER = SPINEL_PROP_THREAD__BEGIN + 0, // [6] + SPINEL_PROP_THREAD_PARENT = SPINEL_PROP_THREAD__BEGIN + 1, // LADDR, SADDR [ES] + SPINEL_PROP_THREAD_CHILD_TABLE = SPINEL_PROP_THREAD__BEGIN + 2, // [A(T(ES))] + SPINEL_PROP_THREAD__END = 0x60, + + SPINEL_PROP_IPV6__BEGIN = 0x60, + SPINEL_PROP_IPV6_LL_ADDR = SPINEL_PROP_IPV6__BEGIN + 0, // [6] + SPINEL_PROP_IPV6_ML_ADDR = SPINEL_PROP_IPV6__BEGIN + 1, // [6C] + SPINEL_PROP_IPV6_ML_PREFIX = SPINEL_PROP_IPV6__BEGIN + 2, // [6C] + SPINEL_PROP_IPV6_ADDRESS_TABLE = SPINEL_PROP_IPV6__BEGIN + 3, // array(ipv6addr,prefixlen,flags) [A(6CL)] + SPINEL_PROP_IPV6_ROUTE_TABLE = SPINEL_PROP_IPV6__BEGIN + 4, // array(ipv6prefix,prefixlen,nexthop,flags) [A(6C6L)] + SPINEL_PROP_IPV6__END = 0x70, + + SPINEL_PROP_STREAM__BEGIN = 112, + SPINEL_PROP_STREAM_DEBUG = SPINEL_PROP_STREAM__BEGIN + 0, // [U] + SPINEL_PROP_STREAM_RAW = SPINEL_PROP_STREAM__BEGIN + 1, // [D] + SPINEL_PROP_STREAM_NET = SPINEL_PROP_STREAM__BEGIN + 2, // [D] + SPINEL_PROP_STREAM_NET_INSECURE = SPINEL_PROP_STREAM__BEGIN + 3, // [D] + SPINEL_PROP_STREAM__END = 128, + + SPINEL_PROP_15_4_PIB__BEGIN = 1024, + // For direct access to the 802.15.4 PID. + // Individual registers are fetched using + // `SPINEL_PROP_15_4_PIB__BEGIN+[PIB_IDENTIFIER]` + // Only supported if SPINEL_CAP_15_4_PIB is set. + SPINEL_PROP_15_4_PIB__END = 1280, + + SPINEL_PROP_NEST__BEGIN = 15296, + SPINEL_PROP_NEST__END = 15360, + + SPINEL_PROP_VENDOR__BEGIN = 15360, + SPINEL_PROP_VENDOR__END = 16384, + + SPINEL_PROP_EXPERIMENTAL__BEGIN = 2000000, + SPINEL_PROP_EXPERIMENTAL__END = 2097152, +} spinel_prop_key_t; + +// ---------------------------------------------------------------------------- + +#define SPINEL_HEADER_FLAG 0x80 + +#define SPINEL_HEADER_TID_SHIFT 0 +#define SPINEL_HEADER_TID_MASK (15 << SPINEL_HEADER_TID_SHIFT) + +#define SPINEL_HEADER_IID_SHIFT 4 +#define SPINEL_HEADER_IID_MASK (3 << SPINEL_HEADER_IID_SHIFT) + +#define SPINEL_HEADER_IID_0 (0 << SPINEL_HEADER_IID_SHIFT) +#define SPINEL_HEADER_IID_1 (1 << SPINEL_HEADER_IID_SHIFT) +#define SPINEL_HEADER_IID_2 (2 << SPINEL_HEADER_IID_SHIFT) +#define SPINEL_HEADER_IID_3 (3 << SPINEL_HEADER_IID_SHIFT) + +#define SPINEL_HEADER_GET_IID(x) (((x) & SPINEL_HEADER_IID_MASK) >> SPINEL_HEADER_IID_SHIFT) +#define SPINEL_HEADER_GET_TID(x) (spinel_tid_t)(((x)&SPINEL_HEADER_TID_MASK)>>SPINEL_HEADER_TID_SHIFT) + +#define SPINEL_GET_NEXT_TID(x) (spinel_tid_t)((x)>=0xF?1:(x)+1) + +#define SPINEL_BEACON_THREAD_FLAG_VERSION_SHIFT 4 +#define SPINEL_BEACON_THREAD_FLAG_VERSION_MASK (0xf << SPINEL_BEACON_THREAD_FLAG_VERSION_SHIFT) +#define SPINEL_BEACON_THREAD_FLAG_JOINABLE (1 << 0) +#define SPINEL_BEACON_THREAD_FLAG_NATIVE (1 << 3) + +// ---------------------------------------------------------------------------- + +enum +{ + SPINEL_DATATYPE_NULL_C = 0, + SPINEL_DATATYPE_VOID_C = '.', + SPINEL_DATATYPE_BOOL_C = 'b', + SPINEL_DATATYPE_UINT8_C = 'C', + SPINEL_DATATYPE_INT8_C = 'c', + SPINEL_DATATYPE_UINT16_C = 'S', + SPINEL_DATATYPE_INT16_C = 's', + SPINEL_DATATYPE_UINT32_C = 'L', + SPINEL_DATATYPE_INT32_C = 'l', + SPINEL_DATATYPE_UINT_PACKED_C = 'i', + SPINEL_DATATYPE_IPv6ADDR_C = '6', + SPINEL_DATATYPE_EUI64_C = 'E', + SPINEL_DATATYPE_EUI48_C = 'e', + SPINEL_DATATYPE_DATA_C = 'D', + SPINEL_DATATYPE_UTF8_C = 'U', //!< Zero-Terminated UTF8-Encoded String + SPINEL_DATATYPE_STRUCT_C = 'T', + SPINEL_DATATYPE_ARRAY_C = 'A', +}; + +typedef char spinel_datatype_t; + +#define SPINEL_DATATYPE_NULL_S "" +#define SPINEL_DATATYPE_VOID_S "." +#define SPINEL_DATATYPE_BOOL_S "b" +#define SPINEL_DATATYPE_UINT8_S "C" +#define SPINEL_DATATYPE_INT8_S "c" +#define SPINEL_DATATYPE_UINT16_S "S" +#define SPINEL_DATATYPE_INT16_S "s" +#define SPINEL_DATATYPE_UINT32_S "L" +#define SPINEL_DATATYPE_INT32_S "l" +#define SPINEL_DATATYPE_UINT_PACKED_S "i" +#define SPINEL_DATATYPE_IPv6ADDR_S "6" +#define SPINEL_DATATYPE_EUI64_S "E" +#define SPINEL_DATATYPE_EUI48_S "e" +#define SPINEL_DATATYPE_DATA_S "D" +#define SPINEL_DATATYPE_UTF8_S "U" //!< Zero-Terminated UTF8-Encoded String +#define SPINEL_DATATYPE_STRUCT_S "T" +#define SPINEL_DATATYPE_ARRAY_S "A" + +SPINEL_API_EXTERN spinel_ssize_t spinel_datatype_pack(uint8_t *data_out, spinel_size_t data_len, + const char *pack_format, ...); +SPINEL_API_EXTERN spinel_ssize_t spinel_datatype_vpack(uint8_t *data_out, spinel_size_t data_len, + const char *pack_format, va_list args); +SPINEL_API_EXTERN spinel_ssize_t spinel_datatype_unpack(const uint8_t *data_in, spinel_size_t data_len, + const char *pack_format, ...); +SPINEL_API_EXTERN spinel_ssize_t spinel_datatype_vunpack(const uint8_t *data_in, spinel_size_t data_len, + const char *pack_format, va_list args); + +SPINEL_API_EXTERN spinel_ssize_t spinel_packed_uint_decode(const uint8_t *bytes, spinel_size_t len, + unsigned int *value); +SPINEL_API_EXTERN spinel_ssize_t spinel_packed_uint_encode(uint8_t *bytes, spinel_size_t len, unsigned int value); +SPINEL_API_EXTERN spinel_ssize_t spinel_packed_uint_size(unsigned int value); + +SPINEL_API_EXTERN const char *spinel_next_packed_datatype(const char *pack_format); + +// ---------------------------------------------------------------------------- + +__END_DECLS + +#endif /* defined(SPINEL_HEADER_INCLUDED) */