blob: 299be12efe4fc0f2a123ca822793c6acc5b8f24b [file] [log] [blame]
/*
* 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 for the CoAP service.
*/
#include "cli_udp.hpp"
#include <openthread/message.h>
#include <openthread/nat64.h>
#include <openthread/udp.h>
#include "cli/cli.hpp"
#include "common/encoding.hpp"
namespace ot {
namespace Cli {
UdpExample::UdpExample(otInstance *aInstance, OutputImplementer &aOutputImplementer)
: Utils(aInstance, aOutputImplementer)
, mLinkSecurityEnabled(true)
{
ClearAllBytes(mSocket);
}
/**
* @cli udp bind
* @code
* udp bind :: 1234
* Done
* @endcode
* @code
* udp bind -u :: 1234
* Done
* @endcode
* @code
* udp bind -b :: 1234
* Done
* @endcode
* @cparam udp bind [@ca{netif}] @ca{ip} @ca{port}
* - `netif`: The binding network interface, which is determined as follows:
* - No value (leaving out this parameter from the command): Thread network interface is used.
* - `-u`: Unspecified network interface, which means that the UDP/IPv6 stack determines which
* network interface to bind the socket to.
* - `-b`: Backbone network interface is used.
* - `ip`: IPv6 address to bind to. If you wish to have the UDP/IPv6 stack assign the binding
* IPv6 address, then you can use the following value to use the unspecified
* IPv6 address: `::`. Each example uses the unspecified IPv6 address.
* - `port`: UDP port number to bind to. Each of the examples is using port number 1234.
* @par
* Assigns an IPv6 address and a port to an open socket, which binds the socket for communication.
* Assigning the IPv6 address and port is referred to as naming the socket. @moreinfo{@udp}.
* @sa otUdpBind
*/
template <> otError UdpExample::Process<Cmd("bind")>(Arg aArgs[])
{
otError error;
otSockAddr sockaddr;
otNetifIdentifier netif = OT_NETIF_THREAD;
if (aArgs[0] == "-u")
{
netif = OT_NETIF_UNSPECIFIED;
aArgs++;
}
else if (aArgs[0] == "-b")
{
netif = OT_NETIF_BACKBONE;
aArgs++;
}
SuccessOrExit(error = aArgs[0].ParseAsIp6Address(sockaddr.mAddress));
SuccessOrExit(error = aArgs[1].ParseAsUint16(sockaddr.mPort));
VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
error = otUdpBind(GetInstancePtr(), &mSocket, &sockaddr, netif);
exit:
return error;
}
/**
* @cli udp connect
* @code
* udp connect fdde:ad00:beef:0:bb1:ebd6:ad10:f33 1234
* Done
* @endcode
* @code
* udp connect 172.17.0.1 1234
* Connecting to synthesized IPv6 address: fdde:ad00:beef:2:0:0:ac11:1
* Done
* @endcode
* @cparam udp connect @ca{ip} @ca{port}
* The following parameters are required:
* - `ip`: IP address of the peer.
* - `port`: UDP port number of the peer.
* The address can be an IPv4 address, which gets synthesized to an IPv6 address
* using the preferred NAT64 prefix from the network data. The command returns
* `InvalidState` when the preferred NAT64 prefix is unavailable.
* @par api_copy
* #otUdpConnect
* @moreinfo{@udp}.
*/
template <> otError UdpExample::Process<Cmd("connect")>(Arg aArgs[])
{
otError error;
otSockAddr sockaddr;
bool nat64Synth;
SuccessOrExit(error = Interpreter::ParseToIp6Address(GetInstancePtr(), aArgs[0], sockaddr.mAddress, nat64Synth));
if (nat64Synth)
{
OutputFormat("Connecting to synthesized IPv6 address: ");
OutputIp6AddressLine(sockaddr.mAddress);
}
SuccessOrExit(error = aArgs[1].ParseAsUint16(sockaddr.mPort));
VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
error = otUdpConnect(GetInstancePtr(), &mSocket, &sockaddr);
exit:
return error;
}
/**
* @cli udp close
* @code
* udp close
* Done
* @endcode
* @par api_copy
* #otUdpClose
*/
template <> otError UdpExample::Process<Cmd("close")>(Arg aArgs[])
{
OT_UNUSED_VARIABLE(aArgs);
return otUdpClose(GetInstancePtr(), &mSocket);
}
/**
* @cli udp open
* @code
* udp open
* Done
* @endcode
* @par api_copy
* #otUdpOpen
*/
template <> otError UdpExample::Process<Cmd("open")>(Arg aArgs[])
{
OT_UNUSED_VARIABLE(aArgs);
otError error;
VerifyOrExit(!otUdpIsOpen(GetInstancePtr(), &mSocket), error = OT_ERROR_ALREADY);
error = otUdpOpen(GetInstancePtr(), &mSocket, HandleUdpReceive, this);
exit:
return error;
}
/**
* @cli udp send
* @code
* udp send hello
* Done
* @endcode
* @code
* udp send -t hello
* Done
* @endcode
* @code
* udp send -x 68656c6c6f
* Done
* @endcode
* @code
* udp send -s 800
* Done
* @endcode
* @code
* udp send fdde:ad00:beef:0:bb1:ebd6:ad10:f33 1234 hello
* Done
* @endcode
* @code
* udp send 172.17.0.1 1234 hello
* Sending to synthesized IPv6 address: fdde:ad00:beef:2:0:0:ac11:1
* Done
* @endcode
* @code
* udp send fdde:ad00:beef:0:bb1:ebd6:ad10:f33 1234 -t hello
* Done
* @endcode
* @code
* udp send fdde:ad00:beef:0:bb1:ebd6:ad10:f33 1234 -x 68656c6c6f
* Done
* @endcode
* @code
* udp send fdde:ad00:beef:0:bb1:ebd6:ad10:f33 1234 -s 800
* Done
* @endcode
* @cparam udp send [@ca{ip} @ca{port}] [@ca{type}] @ca{value}
* The `ip` and `port` are optional as a pair, but if you specify one you must
* specify the other. If `ip` and `port` are not specified, the socket peer address
* is used from `udp connect`.
* - `ip`: Destination address. This address can be either an IPv4 or IPv6 address,
* An IPv4 address gets synthesized to an IPv6 address with the preferred
* NAT64 prefix from the network data. (If the preferred NAT64 prefix
* is unavailable, the command returns `InvalidState`).
* - `port`: UDP destination port.
* - `type`/`value` combinations:
* - `-t`: The payload in the `value` parameter is treated as text. If no `type` value
* is entered, the payload in the `value` parameter is also treated as text.
* - `-s`: Auto-generated payload with the specified length given in the `value` parameter.
* - `-x`: Binary data in hexadecimal representation given in the `value` parameter.
* @par
* Sends a UDP message using the socket. @moreinfo{@udp}.
* @csa{udp open}
* @csa{udp bind}
* @csa{udp connect}
* @sa otUdpSend
*/
template <> otError UdpExample::Process<Cmd("send")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
otMessage *message = nullptr;
otMessageInfo messageInfo;
otMessageSettings messageSettings = {mLinkSecurityEnabled, OT_MESSAGE_PRIORITY_NORMAL};
ClearAllBytes(messageInfo);
// Possible argument formats:
//
// send <text>
// send <type> <value>
// send <ip> <port> <text>
// send <ip> <port> <type> <value>
if (!aArgs[2].IsEmpty())
{
bool nat64Synth;
SuccessOrExit(
error = Interpreter::ParseToIp6Address(GetInstancePtr(), aArgs[0], messageInfo.mPeerAddr, nat64Synth));
if (nat64Synth)
{
OutputFormat("Sending to synthesized IPv6 address: ");
OutputIp6AddressLine(messageInfo.mPeerAddr);
}
SuccessOrExit(error = aArgs[1].ParseAsUint16(messageInfo.mPeerPort));
aArgs += 2;
}
message = otUdpNewMessage(GetInstancePtr(), &messageSettings);
VerifyOrExit(message != nullptr, error = OT_ERROR_NO_BUFS);
if (aArgs[0] == "-s")
{
// Auto-generated payload with a given length
uint16_t payloadLength;
SuccessOrExit(error = aArgs[1].ParseAsUint16(payloadLength));
SuccessOrExit(error = PrepareAutoGeneratedPayload(*message, payloadLength));
}
else if (aArgs[0] == "-x")
{
// Binary hex data payload
VerifyOrExit(!aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
SuccessOrExit(error = PrepareHexStringPayload(*message, aArgs[1].GetCString()));
}
else
{
// Text payload (same as without specifying the type)
if (aArgs[0] == "-t")
{
aArgs++;
}
VerifyOrExit(!aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
SuccessOrExit(error = otMessageAppend(message, aArgs[0].GetCString(), aArgs[0].GetLength()));
}
SuccessOrExit(error = otUdpSend(GetInstancePtr(), &mSocket, message, &messageInfo));
message = nullptr;
exit:
if (message != nullptr)
{
otMessageFree(message);
}
return error;
}
template <> otError UdpExample::Process<Cmd("linksecurity")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
/**
* @cli udp linksecurity
* @code
* udp linksecurity
* Enabled
* Done
* @endcode
* @par
* Indicates whether link security is enabled or disabled.
*/
if (aArgs[0].IsEmpty())
{
OutputEnabledDisabledStatus(mLinkSecurityEnabled);
}
/**
* @cli udp linksecurity (enable,disable)
* @code
* udp linksecurity enable
* Done
* @endcode
* @code
* udp linksecurity disable
* Done
* @endcode
* @par
* Enables or disables link security.
*/
else
{
error = Interpreter::ParseEnableOrDisable(aArgs[0], mLinkSecurityEnabled);
}
return error;
}
otError UdpExample::PrepareAutoGeneratedPayload(otMessage &aMessage, uint16_t aPayloadLength)
{
otError error = OT_ERROR_NONE;
uint8_t character = '0';
for (; aPayloadLength != 0; aPayloadLength--)
{
SuccessOrExit(error = otMessageAppend(&aMessage, &character, sizeof(character)));
switch (character)
{
case '9':
character = 'A';
break;
case 'Z':
character = 'a';
break;
case 'z':
character = '0';
break;
default:
character++;
break;
}
}
exit:
return error;
}
otError UdpExample::PrepareHexStringPayload(otMessage &aMessage, const char *aHexString)
{
static constexpr uint16_t kBufferSize = 50;
otError error;
uint8_t buf[kBufferSize];
uint16_t length;
bool done = false;
while (!done)
{
length = sizeof(buf);
error = ot::Utils::CmdLineParser::ParseAsHexStringSegment(aHexString, length, buf);
VerifyOrExit((error == OT_ERROR_NONE) || (error == OT_ERROR_PENDING));
done = (error == OT_ERROR_NONE);
SuccessOrExit(error = otMessageAppend(&aMessage, buf, length));
}
exit:
return error;
}
otError UdpExample::Process(Arg aArgs[])
{
#define CmdEntry(aCommandString) \
{ \
aCommandString, &UdpExample::Process<Cmd(aCommandString)> \
}
static constexpr Command kCommands[] = {
CmdEntry("bind"), CmdEntry("close"), CmdEntry("connect"),
CmdEntry("linksecurity"), CmdEntry("open"), CmdEntry("send"),
};
static_assert(BinarySearch::IsSorted(kCommands), "kCommands is not sorted");
otError error = OT_ERROR_INVALID_COMMAND;
const Command *command;
if (aArgs[0].IsEmpty() || (aArgs[0] == "help"))
{
OutputCommandTable(kCommands);
ExitNow(error = aArgs[0].IsEmpty() ? error : OT_ERROR_NONE);
}
command = BinarySearch::Find(aArgs[0].GetCString(), kCommands);
VerifyOrExit(command != nullptr);
error = (this->*command->mHandler)(aArgs + 1);
exit:
return error;
}
void UdpExample::HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo)
{
static_cast<UdpExample *>(aContext)->HandleUdpReceive(aMessage, aMessageInfo);
}
void UdpExample::HandleUdpReceive(otMessage *aMessage, const otMessageInfo *aMessageInfo)
{
char buf[1500];
int length;
OutputFormat("%d bytes from ", otMessageGetLength(aMessage) - otMessageGetOffset(aMessage));
OutputIp6Address(aMessageInfo->mPeerAddr);
OutputFormat(" %d ", aMessageInfo->mPeerPort);
length = otMessageRead(aMessage, otMessageGetOffset(aMessage), buf, sizeof(buf) - 1);
buf[length] = '\0';
OutputLine("%s", buf);
}
} // namespace Cli
} // namespace ot