diff --git a/Makefile.am b/Makefile.am index 87dba9c8b..bc2a996c0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -42,6 +42,7 @@ AM_DISTCHECK_CONFIGURE_FLAGS = \ --enable-dhcp6-client \ --enable-dhcp6-server \ --enable-dns-client \ + --enable-application-coap \ $(NULL) SUBDIRS = \ diff --git a/src/cli/Makefile.am b/src/cli/Makefile.am index 795facac2..5e1f1f3cd 100644 --- a/src/cli/Makefile.am +++ b/src/cli/Makefile.am @@ -45,6 +45,10 @@ libopenthread_cli_a_SOURCES = \ cli_uart.cpp \ cli_udp.cpp \ $(NULL) + +if OPENTHREAD_ENABLE_APPLICATION_COAP +libopenthread_cli_a_SOURCES += cli_coap.cpp +endif noinst_HEADERS = \ cli.hpp \ @@ -54,6 +58,10 @@ noinst_HEADERS = \ cli_server.hpp \ cli_udp.hpp \ $(NULL) + +if OPENTHREAD_ENABLE_APPLICATION_COAP +noinst_HEADERS += cli_coap.hpp +endif if OPENTHREAD_BUILD_COVERAGE CLEANFILES = $(wildcard *.gcda *.gcno) diff --git a/src/cli/README.md b/src/cli/README.md index c0be2d6ee..85baaa713 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -14,6 +14,7 @@ OpenThread test scripts use the CLI to execute test cases. * [child](#child-list) * [childmax](#childmax) * [childtimeout](#childtimeout) +* [coap](#coap-server-phase) * [commissioner](#commissioner-start-provisioningurl) * [contextreusedelay](#contextreusedelay) * [counter](#counter) @@ -269,6 +270,57 @@ Set the Thread Child Timeout value. Done ``` +### coap server \ + +Starts and stops the simple CoAP server. + +* phase: Either "start" or "stop" the server. + +```bash +> coap server start +Server started with resource '': Done +> coap server stop +Server stopped: Done +``` + +### coap server name \[URI\] + +Outputs the currently used URI String of the CoAP resource. + +* URI: If provided the URI String will be changed to the new value. + +```bash +> coap server name +Current resource name is '': Done +> coap server name test +Changing resource name to 'test': Done +``` + +### coap client \ \ \ \[payload\] \[messageType\] + +Simple CoAP client that can send Non-/Confirmable GET/PUT/POST/DELETE messages. + +* method: CoAP method to be used (GET/PUT/POST/DELETE). +* IPv6address: IP address of the CoAP server to query. +* URI: URI String of the resource on the CoAP server. +* payload: In case of PUT/POST/DELETE a payload can be encapsulated. +* messageType: Switch between confirmable ("con") and non-confirmable (default). + +```bash +> coap client get fdde:ad00:beef:0:dbaa:f1d0:8afb:30dc test +Sending CoAP message: Done +Received CoAP request from [fdde:ad00:beef:0:dbaa:f1d0:8afb:30dc]: GET +CoAP response sent successfully! +Received CoAP response with payload: 30 +> coap client put fdde:ad00:beef:0:dbaa:f1d0:8afb:30dc test non-con somePayload +Sending CoAP message: Done +> coap client put fdde:ad00:beef:0:dbaa:f1d0:8afb:30dc test con 123 +Sending CoAP message: Done +Received CoAP request from [fdde:ad00:beef:0:dbaa:f1d0:8afb:30dc]: PUT with payload: ba 00 00 20 +CoAP response sent successfully! +Received CoAP response +``` + ### commissioner start \ Start the Commissioner role. diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index c6a01169f..12aa9ace1 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -66,6 +66,9 @@ #include "cli.hpp" #include "cli_dataset.hpp" #include "cli_uart.hpp" +#if OPENTHREAD_ENABLE_APPLICATION_COAP +#include "cli_coap.hpp" +#endif using Thread::Encoding::BigEndian::HostSwap16; using Thread::Encoding::BigEndian::HostSwap32; @@ -84,6 +87,9 @@ const struct Command Interpreter::sCommands[] = { "child", &Interpreter::ProcessChild }, { "childmax", &Interpreter::ProcessChildMax }, { "childtimeout", &Interpreter::ProcessChildTimeout }, +#if OPENTHREAD_ENABLE_APPLICATION_COAP + { "coap", &Interpreter::ProcessCoap }, +#endif #if OPENTHREAD_ENABLE_COMMISSIONER { "commissioner", &Interpreter::ProcessCommissioner }, #endif @@ -602,6 +608,17 @@ exit: AppendResult(error); } +#if OPENTHREAD_ENABLE_APPLICATION_COAP + +void Interpreter::ProcessCoap(int argc, char *argv[]) +{ + ThreadError error; + error = Coap::Process(mInstance, argc, argv, *sServer); + AppendResult(error); +} + +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP + void Interpreter::ProcessContextIdReuseDelay(int argc, char *argv[]) { ThreadError error = kThreadError_None; diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index 9789af3ed..ef8346427 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -49,6 +49,10 @@ #include #include +#if OPENTHREAD_ENABLE_APPLICATION_COAP +#include +#endif + #ifndef OTDLL #include #include @@ -162,6 +166,9 @@ private: void ProcessChild(int argc, char *argv[]); void ProcessChildTimeout(int argc, char *argv[]); void ProcessChildMax(int argc, char *argv[]); +#if OPENTHREAD_ENABLE_APPLICATION_COAP + void ProcessCoap(int argc, char *argv[]); +#endif //OPENTHREAD_ENABLE_APPLICATION_COAP #if OPENTHREAD_ENABLE_COMMISSIONER void ProcessCommissioner(int argc, char *argv[]); #endif // OPENTHREAD_ENABLE_COMMISSIONER diff --git a/src/cli/cli_coap.cpp b/src/cli/cli_coap.cpp new file mode 100644 index 000000000..8ab04686c --- /dev/null +++ b/src/cli/cli_coap.cpp @@ -0,0 +1,384 @@ +/* + * Copyright (c) 2017, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file implements a simple CLI coap server and client. + */ + +#include +#include +#include +#include + +namespace Thread { +namespace Cli { + +const CoapCommand Coap::sCommands[] = +{ + { "client", &ProcessClient }, + { "server", &ProcessServer } +}; + +Server *Coap::sServer; +otCoapResource Coap::sResource; +otInstance *Coap::sInstance; +char Coap::sUriPath[kMaxUriLength]; + +void Coap::PrintPayload(otMessage *aMessage) +{ + uint8_t buf[kMaxBufferSize]; + uint16_t bytesToPrint; + uint16_t bytesPrinted = 0; + uint16_t length = otMessageGetLength(aMessage) - otMessageGetOffset(aMessage); + + if (length > 0) + { + sServer->OutputFormat(" with payload: "); + + while (length > 0) + { + bytesToPrint = (length < sizeof(buf)) ? length : sizeof(buf); + otMessageRead(aMessage, otMessageGetOffset(aMessage) + bytesPrinted, buf, bytesToPrint); + + OutputBytes(buf, static_cast(bytesToPrint)); + + length -= bytesToPrint; + bytesPrinted += bytesToPrint; + } + } + sServer->OutputFormat("\r\n"); +} + +void Coap::ConvertToLower(char *aString) +{ + uint8_t i = 0; + while (aString[i]) + { + aString[i] = tolower(aString[i]); + i++; + } +} + +void Coap::OutputBytes(const uint8_t *aBytes, uint8_t aLength) +{ + for (int i = 0; i < aLength; i++) + { + sServer->OutputFormat("%02x ", aBytes[i]); + } +} + +ThreadError Coap::Process(otInstance *aInstance, int argc, char *argv[], Server &aServer) +{ + ThreadError error = kThreadError_None; + + sInstance = aInstance; + sServer = &aServer; + sResource.mUriPath = sUriPath; + sResource.mContext = aInstance; + sResource.mHandler = (otCoapRequestHandler) &Coap::s_HandleServerResponse; + + VerifyOrExit(argc > 0, error = kThreadError_InvalidArgs); + for (unsigned int i = 0; i < sizeof(sCommands) / sizeof(sCommands[0]); i++) + { + if (strcmp(argv[0], sCommands[i].mName) == 0) + { + error = sCommands[i].mCommand(argc - 1, argv + 1); + break; + } + } + +exit: + return error; +} + +ThreadError Coap::ProcessServer(int argc, char *argv[]) +{ + ThreadError error = kThreadError_None; + + VerifyOrExit(argc > 0, error = kThreadError_InvalidArgs); + + ConvertToLower(argv[0]); + if (strcmp(argv[0], "start") == 0) + { + SuccessOrExit(error = otCoapServerStart(sInstance)); + SuccessOrExit(error = otCoapServerAddResource(sInstance, &sResource)); + sServer->OutputFormat("Server started with resource '%s': ", sResource.mUriPath); + } + else if (strcmp(argv[0], "stop") == 0) + { + otCoapServerRemoveResource(sInstance, &sResource); + SuccessOrExit(error = otCoapServerStop(sInstance)); + sServer->OutputFormat("Server stopped: "); + } + else if (strcmp(argv[0], "name") == 0) + { + if (argc > 1) + { + strlcpy(sUriPath, argv[1], kMaxUriLength); + sServer->OutputFormat("Changing resource name to '%s': ", sResource.mUriPath); + } + else + { + sServer->OutputFormat("Current resource name is '%s': ", sResource.mUriPath); + } + } + else + { + ExitNow(error = kThreadError_InvalidArgs); + } + +exit: + return error; +} + +void OTCALL Coap::s_HandleServerResponse(void *aContext, otCoapHeader *aHeader, otMessage *aMessage, + otMessageInfo *aMessageInfo) +{ + static_cast(aContext)->HandleServerResponse(aHeader, aMessage, aMessageInfo); +} + +void Coap::HandleServerResponse(otCoapHeader *aHeader, otMessage *aMessage, otMessageInfo *aMessageInfo) +{ + ThreadError error = kThreadError_None; + otCoapHeader responseHeader; + otMessage *responseMessage; + otCoapCode responseCode = kCoapCodeEmpty ; + char responseContent = '0'; + + sServer->OutputFormat("Received CoAP request from [%x:%x:%x:%x:%x:%x:%x:%x]: ", + HostSwap16(aMessageInfo->mSockAddr.mFields.m16[0]), + HostSwap16(aMessageInfo->mSockAddr.mFields.m16[1]), + HostSwap16(aMessageInfo->mSockAddr.mFields.m16[2]), + HostSwap16(aMessageInfo->mSockAddr.mFields.m16[3]), + HostSwap16(aMessageInfo->mSockAddr.mFields.m16[4]), + HostSwap16(aMessageInfo->mSockAddr.mFields.m16[5]), + HostSwap16(aMessageInfo->mSockAddr.mFields.m16[6]), + HostSwap16(aMessageInfo->mSockAddr.mFields.m16[7])); + + switch (otCoapHeaderGetCode(aHeader)) + { + case kCoapRequestGet: + sServer->OutputFormat("GET"); + break; + + case kCoapRequestDelete: + sServer->OutputFormat("DELETE"); + break; + + case kCoapRequestPut: + sServer->OutputFormat("PUT"); + break; + + case kCoapRequestPost: + sServer->OutputFormat("POST"); + break; + + default: + sServer->OutputFormat("Undefined\r\n"); + return; + } + + PrintPayload(aMessage); + + if ((otCoapHeaderGetType(aHeader) == kCoapTypeConfirmable) || otCoapHeaderGetCode(aHeader) == kCoapRequestGet) + { + if (otCoapHeaderGetCode(aHeader) == kCoapRequestGet) + { + responseCode = kCoapResponseContent; + } + else + { + responseCode = kCoapResponseValid; + } + + otCoapHeaderInit(&responseHeader, kCoapTypeAcknowledgment, responseCode); + otCoapHeaderSetMessageId(&responseHeader, otCoapHeaderGetMessageId(aHeader)); + otCoapHeaderSetToken(&responseHeader, otCoapHeaderGetToken(aHeader), otCoapHeaderGetTokenLength(aHeader)); + + if (otCoapHeaderGetCode(aHeader) == kCoapRequestGet) + { + otCoapHeaderSetPayloadMarker(&responseHeader); + } + + responseMessage = otCoapNewMessage(sInstance, &responseHeader); + VerifyOrExit(responseMessage != NULL, error = kThreadError_NoBufs); + + if (otCoapHeaderGetCode(aHeader) == kCoapRequestGet) + { + SuccessOrExit(error = otMessageAppend(responseMessage, &responseContent, sizeof(responseContent))); + } + + SuccessOrExit(error = otCoapSendResponse(sInstance, responseMessage, aMessageInfo)); + } + +exit: + + if (error != kThreadError_None && responseMessage != NULL) + { + sServer->OutputFormat("Cannot send CoAP response message: Error %d\r\n", error); + otMessageFree(responseMessage); + } + else + { + if (responseCode >= kCoapResponseCodeMin) + { + sServer->OutputFormat("CoAP response sent successfully!\r\n"); + } + } +} + +ThreadError Coap::ProcessClient(int argc, char *argv[]) +{ + ThreadError error = kThreadError_None; + otMessage *message = otCoapNewMessage(sInstance, NULL); + otMessageInfo messageInfo; + otCoapHeader header; + + // Default parameters + char coapUri[kMaxUriLength] = "test"; + otCoapType coapType = kCoapTypeNonConfirmable; + otCoapCode coapCode = kCoapRequestGet; + otIp6Address coapDestinationIp; + + VerifyOrExit(argc > 0, error = kThreadError_InvalidArgs); + + // CoAP-Code + ConvertToLower(argv[0]); + if (strcmp(argv[0], "get") == 0) + { + coapCode = kCoapRequestGet; + } + else if (strcmp(argv[0], "post") == 0) + { + coapCode = kCoapRequestPost; + } + else if (strcmp(argv[0], "put") == 0) + { + coapCode = kCoapRequestPut; + } + else if (strcmp(argv[0], "delete") == 0) + { + coapCode = kCoapRequestDelete; + } + else + { + ExitNow(error = kThreadError_Parse); + } + + // Destination IPv6 address + if (argc > 1) + { + SuccessOrExit(error = otIp6AddressFromString(argv[1], &coapDestinationIp)); + } + else + { + ExitNow(error = kThreadError_InvalidArgs); + } + + // CoAP-URI + if (argc > 2) + { + strlcpy(coapUri, argv[2], kMaxUriLength); + } + else + { + ExitNow(error = kThreadError_InvalidArgs); + } + + // CoAP-Type + ConvertToLower(argv[3]); + if ((argc > 3) && (strcmp(argv[3], "con") == 0)) + { + coapType = kCoapTypeConfirmable; + } + + otCoapHeaderInit(&header, coapType, coapCode); + otCoapHeaderGenerateToken(&header, Thread::Coap::Header::kDefaultTokenLength); + SuccessOrExit(error = otCoapHeaderAppendUriPathOptions(&header, coapUri)); + otCoapHeaderSetPayloadMarker(&header); + + message = otCoapNewMessage(sInstance, &header); + VerifyOrExit(message != NULL, error = kThreadError_NoBufs); + + // Embed content into message if given + if (argc > 4) + { + SuccessOrExit(error = otMessageAppend(message, &argv[4], sizeof(argv[4]))); + } + + memset(&messageInfo, 0, sizeof(messageInfo)); + messageInfo.mPeerAddr = coapDestinationIp; + messageInfo.mPeerPort = OT_DEFAULT_COAP_PORT; + messageInfo.mInterfaceId = OT_NETIF_INTERFACE_ID_THREAD; + + if ((coapType == kCoapTypeConfirmable) || coapCode == kCoapRequestGet) + { + error = otCoapSendRequest(sInstance, message, &messageInfo, + (otCoapResponseHandler) &Coap::s_HandleClientResponse, NULL); + } + else + { + error = otCoapSendRequest(sInstance, message, &messageInfo, NULL, NULL); + } + + sServer->OutputFormat("Sending CoAP message: "); + +exit: + if ((error != kThreadError_None) && (message != NULL)) + { + otMessageFree(message); + } + return error; +} + +void OTCALL Coap::s_HandleClientResponse(void *aContext, otCoapHeader *aHeader, otMessage *aMessage, + otMessageInfo *aMessageInfo, ThreadError aResult) +{ + static_cast(aContext)->HandleClientResponse(aHeader, aMessage, aMessageInfo, aResult); +} + +void Coap::HandleClientResponse(otCoapHeader *aHeader, otMessage *aMessage, otMessageInfo *aMessageInfo, + ThreadError aResult) +{ + if (aResult != kThreadError_None) + { + sServer->OutputFormat("Error receiving CoAP response message: %d", aResult); + } + else + { + sServer->OutputFormat("Received CoAP response"); + PrintPayload(aMessage); + } + + (void)aHeader; + (void)aMessageInfo; +} + +} // namespace Cli +} // namespace Thread diff --git a/src/cli/cli_coap.hpp b/src/cli/cli_coap.hpp new file mode 100644 index 000000000..c80eb2158 --- /dev/null +++ b/src/cli/cli_coap.hpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2017, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file contains definitions for a simple CLI CoAP server and client. + */ + +#ifndef CLI_COAP_HPP_ +#define CLI_COAP_HPP_ + +namespace Thread { +namespace Cli { + +/** + * This structure represents a CLI command. + * + */ +struct CoapCommand +{ + const char *mName; ///< A pointer to the command string. + ThreadError(*mCommand)(int argc, char *argv[]); ///< A function pointer to process the command. +}; + +/** + * This class implements the CLI CoAP server and client. + * + */ +class Coap +{ +public: + /** + * This method interprets a list of CLI arguments. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] argc The number of elements in argv. + * @param[in] argv A pointer to an array of command line arguments. + * @param[in] aServer A reference to the CLI server. + * + */ + static ThreadError Process(otInstance *aInstance, int argc, char *argv[], Server &aServer); + +private: + enum + { + kMaxUriLength = 32, + kMaxBufferSize = 16 + }; + static void PrintPayload(otMessage *aMessage); + static void ConvertToLower(char *aString); + static void OutputBytes(const uint8_t *aBytes, uint8_t aLength); + + static ThreadError ProcessClient(int argc, char *argv[]); + static ThreadError ProcessServer(int argc, char *argv[]); + + static void OTCALL s_HandleServerResponse(void *aContext, otCoapHeader *aHeader, otMessage *aMessage, + otMessageInfo *aMessageInfo); + static void HandleServerResponse(otCoapHeader *aHeader, otMessage *aMessage, otMessageInfo *aMessageInfo); + static void OTCALL s_HandleClientResponse(void *aContext, otCoapHeader *aHeader, otMessage *aMessage, + otMessageInfo *aMessageInfo, ThreadError aResult); + static void HandleClientResponse(otCoapHeader *aHeader, otMessage *aMessage, otMessageInfo *aMessageInfo, + ThreadError aResult); + + static const CoapCommand sCommands[]; + static Server *sServer; + static otCoapResource sResource; + static otInstance *sInstance; + static char sUriPath[kMaxUriLength]; +}; + +} // namespace Cli +} // namespace Thread + +#endif // CLI_COAP_HPP_