blob: 38a778ba3f58e979d65ec69f08a914144f1ea53b [file] [log] [blame]
/*
* Copyright (c) 2019, 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 Commissioner role.
*/
#include "cli_commissioner.hpp"
#include "cli/cli.hpp"
#if OPENTHREAD_CONFIG_COMMISSIONER_ENABLE && OPENTHREAD_FTD
namespace ot {
namespace Cli {
const struct Commissioner::Command Commissioner::sCommands[] = {
{"help", &Commissioner::ProcessHelp}, {"announce", &Commissioner::ProcessAnnounce},
{"energy", &Commissioner::ProcessEnergy}, {"joiner", &Commissioner::ProcessJoiner},
{"mgmtget", &Commissioner::ProcessMgmtGet}, {"mgmtset", &Commissioner::ProcessMgmtSet},
{"panid", &Commissioner::ProcessPanId}, {"provisioningurl", &Commissioner::ProcessProvisioningUrl},
{"sessionid", &Commissioner::ProcessSessionId}, {"start", &Commissioner::ProcessStart},
{"state", &Commissioner::ProcessState}, {"stop", &Commissioner::ProcessStop},
};
otError Commissioner::ProcessHelp(uint8_t aArgsLength, char *aArgs[])
{
OT_UNUSED_VARIABLE(aArgsLength);
OT_UNUSED_VARIABLE(aArgs);
for (const Command &command : sCommands)
{
mInterpreter.OutputFormat("%s\r\n", command.mName);
}
return OT_ERROR_NONE;
}
otError Commissioner::ProcessAnnounce(uint8_t aArgsLength, char *aArgs[])
{
otError error;
long mask;
long count;
long period;
otIp6Address address;
VerifyOrExit(aArgsLength > 4, error = OT_ERROR_INVALID_ARGS);
SuccessOrExit(error = Interpreter::ParseLong(aArgs[1], mask));
SuccessOrExit(error = Interpreter::ParseLong(aArgs[2], count));
SuccessOrExit(error = Interpreter::ParseLong(aArgs[3], period));
SuccessOrExit(error = otIp6AddressFromString(aArgs[4], &address));
SuccessOrExit(error = otCommissionerAnnounceBegin(mInterpreter.mInstance, static_cast<uint32_t>(mask),
static_cast<uint8_t>(count), static_cast<uint16_t>(period),
&address));
exit:
return error;
}
otError Commissioner::ProcessEnergy(uint8_t aArgsLength, char *aArgs[])
{
otError error;
long mask;
long count;
long period;
long scanDuration;
otIp6Address address;
VerifyOrExit(aArgsLength > 5, error = OT_ERROR_INVALID_ARGS);
SuccessOrExit(error = Interpreter::ParseLong(aArgs[1], mask));
SuccessOrExit(error = Interpreter::ParseLong(aArgs[2], count));
SuccessOrExit(error = Interpreter::ParseLong(aArgs[3], period));
SuccessOrExit(error = Interpreter::ParseLong(aArgs[4], scanDuration));
SuccessOrExit(error = otIp6AddressFromString(aArgs[5], &address));
SuccessOrExit(error = otCommissionerEnergyScan(mInterpreter.mInstance, static_cast<uint32_t>(mask),
static_cast<uint8_t>(count), static_cast<uint16_t>(period),
static_cast<uint16_t>(scanDuration), &address,
&Commissioner::HandleEnergyReport, this));
exit:
return error;
}
otError Commissioner::ProcessJoiner(uint8_t aArgsLength, char *aArgs[])
{
otError error;
otExtAddress addr;
const otExtAddress *addrPtr = nullptr;
otJoinerDiscerner discerner;
VerifyOrExit(aArgsLength > 2, error = OT_ERROR_INVALID_ARGS);
memset(&discerner, 0, sizeof(discerner));
if (strcmp(aArgs[2], "*") == 0)
{
// Intentionally empty
}
else if ((error = Interpreter::ParseJoinerDiscerner(aArgs[2], discerner)) == OT_ERROR_NOT_FOUND)
{
VerifyOrExit(Interpreter::Hex2Bin(aArgs[2], addr.m8, sizeof(addr)) == sizeof(addr),
error = OT_ERROR_INVALID_ARGS);
addrPtr = &addr;
}
else if (error != OT_ERROR_NONE)
{
ExitNow();
}
if (strcmp(aArgs[1], "add") == 0)
{
VerifyOrExit(aArgsLength > 3, error = OT_ERROR_INVALID_ARGS);
// Timeout parameter is optional - if not specified, use default value.
unsigned long timeout = kDefaultJoinerTimeout;
if (aArgsLength > 4)
{
SuccessOrExit(error = Interpreter::ParseUnsignedLong(aArgs[4], timeout));
}
if (discerner.mLength)
{
SuccessOrExit(error = otCommissionerAddJoinerWithDiscerner(mInterpreter.mInstance, &discerner, aArgs[3],
static_cast<uint32_t>(timeout)));
}
else
{
SuccessOrExit(error = otCommissionerAddJoiner(mInterpreter.mInstance, addrPtr, aArgs[3],
static_cast<uint32_t>(timeout)));
}
}
else if (strcmp(aArgs[1], "remove") == 0)
{
if (discerner.mLength)
{
SuccessOrExit(error = otCommissionerRemoveJoinerWithDiscerner(mInterpreter.mInstance, &discerner));
}
else
{
SuccessOrExit(error = otCommissionerRemoveJoiner(mInterpreter.mInstance, addrPtr));
}
}
else
{
ExitNow(error = OT_ERROR_INVALID_ARGS);
}
exit:
return error;
}
otError Commissioner::ProcessMgmtGet(uint8_t aArgsLength, char *aArgs[])
{
otError error;
uint8_t tlvs[32];
long value;
int length = 0;
for (uint8_t index = 1; index < aArgsLength; index++)
{
VerifyOrExit(static_cast<size_t>(length) < sizeof(tlvs), error = OT_ERROR_NO_BUFS);
if (strcmp(aArgs[index], "locator") == 0)
{
tlvs[length++] = OT_MESHCOP_TLV_BORDER_AGENT_RLOC;
}
else if (strcmp(aArgs[index], "sessionid") == 0)
{
tlvs[length++] = OT_MESHCOP_TLV_COMM_SESSION_ID;
}
else if (strcmp(aArgs[index], "steeringdata") == 0)
{
tlvs[length++] = OT_MESHCOP_TLV_STEERING_DATA;
}
else if (strcmp(aArgs[index], "joinerudpport") == 0)
{
tlvs[length++] = OT_MESHCOP_TLV_JOINER_UDP_PORT;
}
else if (strcmp(aArgs[index], "binary") == 0)
{
VerifyOrExit(++index < aArgsLength, error = OT_ERROR_INVALID_ARGS);
value = static_cast<long>(strlen(aArgs[index]) + 1) / 2;
VerifyOrExit(static_cast<size_t>(value) <= (sizeof(tlvs) - static_cast<size_t>(length)),
error = OT_ERROR_NO_BUFS);
VerifyOrExit(Interpreter::Hex2Bin(aArgs[index], tlvs + length, static_cast<uint16_t>(value)) == value,
error = OT_ERROR_INVALID_ARGS);
length += value;
}
else
{
ExitNow(error = OT_ERROR_INVALID_ARGS);
}
}
SuccessOrExit(error = otCommissionerSendMgmtGet(mInterpreter.mInstance, tlvs, static_cast<uint8_t>(length)));
exit:
return error;
}
otError Commissioner::ProcessMgmtSet(uint8_t aArgsLength, char *aArgs[])
{
otError error;
otCommissioningDataset dataset;
uint8_t tlvs[32];
long value;
int length = 0;
VerifyOrExit(aArgsLength > 0, error = OT_ERROR_INVALID_ARGS);
memset(&dataset, 0, sizeof(dataset));
for (uint8_t index = 1; index < aArgsLength; index++)
{
VerifyOrExit(static_cast<size_t>(length) < sizeof(tlvs), error = OT_ERROR_NO_BUFS);
if (strcmp(aArgs[index], "locator") == 0)
{
VerifyOrExit(++index < aArgsLength, error = OT_ERROR_INVALID_ARGS);
dataset.mIsLocatorSet = true;
SuccessOrExit(error = Interpreter::Interpreter::ParseLong(aArgs[index], value));
dataset.mLocator = static_cast<uint16_t>(value);
}
else if (strcmp(aArgs[index], "sessionid") == 0)
{
VerifyOrExit(++index < aArgsLength, error = OT_ERROR_INVALID_ARGS);
dataset.mIsSessionIdSet = true;
SuccessOrExit(error = Interpreter::Interpreter::ParseLong(aArgs[index], value));
dataset.mSessionId = static_cast<uint16_t>(value);
}
else if (strcmp(aArgs[index], "steeringdata") == 0)
{
VerifyOrExit(++index < aArgsLength, error = OT_ERROR_INVALID_ARGS);
dataset.mIsSteeringDataSet = true;
length = static_cast<int>((strlen(aArgs[index]) + 1) / 2);
VerifyOrExit(static_cast<size_t>(length) <= OT_STEERING_DATA_MAX_LENGTH, error = OT_ERROR_NO_BUFS);
VerifyOrExit(Interpreter::Hex2Bin(aArgs[index], dataset.mSteeringData.m8, static_cast<uint16_t>(length)) ==
length,
error = OT_ERROR_INVALID_ARGS);
dataset.mSteeringData.mLength = static_cast<uint8_t>(length);
length = 0;
}
else if (strcmp(aArgs[index], "joinerudpport") == 0)
{
VerifyOrExit(++index < aArgsLength, error = OT_ERROR_INVALID_ARGS);
dataset.mIsJoinerUdpPortSet = true;
SuccessOrExit(error = Interpreter::Interpreter::ParseLong(aArgs[index], value));
dataset.mJoinerUdpPort = static_cast<uint16_t>(value);
}
else if (strcmp(aArgs[index], "binary") == 0)
{
VerifyOrExit(++index < aArgsLength, error = OT_ERROR_INVALID_ARGS);
length = static_cast<int>((strlen(aArgs[index]) + 1) / 2);
VerifyOrExit(static_cast<size_t>(length) <= sizeof(tlvs), error = OT_ERROR_NO_BUFS);
VerifyOrExit(Interpreter::Hex2Bin(aArgs[index], tlvs, static_cast<uint16_t>(length)) == length,
error = OT_ERROR_INVALID_ARGS);
}
else
{
ExitNow(error = OT_ERROR_INVALID_ARGS);
}
}
SuccessOrExit(error =
otCommissionerSendMgmtSet(mInterpreter.mInstance, &dataset, tlvs, static_cast<uint8_t>(length)));
exit:
return error;
}
otError Commissioner::ProcessPanId(uint8_t aArgsLength, char *aArgs[])
{
otError error;
long panid;
long mask;
otIp6Address address;
VerifyOrExit(aArgsLength > 3, error = OT_ERROR_INVALID_ARGS);
SuccessOrExit(error = Interpreter::ParseLong(aArgs[1], panid));
SuccessOrExit(error = Interpreter::ParseLong(aArgs[2], mask));
SuccessOrExit(error = otIp6AddressFromString(aArgs[3], &address));
SuccessOrExit(error = otCommissionerPanIdQuery(mInterpreter.mInstance, static_cast<uint16_t>(panid),
static_cast<uint32_t>(mask), &address,
&Commissioner::HandlePanIdConflict, this));
exit:
return error;
}
otError Commissioner::ProcessProvisioningUrl(uint8_t aArgsLength, char *aArgs[])
{
return otCommissionerSetProvisioningUrl(mInterpreter.mInstance, (aArgsLength > 1) ? aArgs[1] : nullptr);
}
otError Commissioner::ProcessSessionId(uint8_t aArgsLength, char *aArgs[])
{
OT_UNUSED_VARIABLE(aArgsLength);
OT_UNUSED_VARIABLE(aArgs);
mInterpreter.OutputFormat("%d\r\n", otCommissionerGetSessionId(mInterpreter.mInstance));
return OT_ERROR_NONE;
}
otError Commissioner::ProcessStart(uint8_t aArgsLength, char *aArgs[])
{
OT_UNUSED_VARIABLE(aArgsLength);
OT_UNUSED_VARIABLE(aArgs);
return otCommissionerStart(mInterpreter.mInstance, &Commissioner::HandleStateChanged,
&Commissioner::HandleJoinerEvent, this);
}
void Commissioner::HandleStateChanged(otCommissionerState aState, void *aContext)
{
static_cast<Commissioner *>(aContext)->HandleStateChanged(aState);
}
void Commissioner::HandleStateChanged(otCommissionerState aState)
{
mInterpreter.OutputFormat("Commissioner: %s\r\n", StateToString(aState));
}
const char *Commissioner::StateToString(otCommissionerState aState)
{
const char *rval = "unknown";
switch (aState)
{
case OT_COMMISSIONER_STATE_DISABLED:
rval = "disabled";
break;
case OT_COMMISSIONER_STATE_PETITION:
rval = "petitioning";
break;
case OT_COMMISSIONER_STATE_ACTIVE:
rval = "active";
break;
}
return rval;
}
void Commissioner::HandleJoinerEvent(otCommissionerJoinerEvent aEvent,
const otJoinerInfo * aJoinerInfo,
const otExtAddress * aJoinerId,
void * aContext)
{
static_cast<Commissioner *>(aContext)->HandleJoinerEvent(aEvent, aJoinerInfo, aJoinerId);
}
void Commissioner::HandleJoinerEvent(otCommissionerJoinerEvent aEvent,
const otJoinerInfo * aJoinerInfo,
const otExtAddress * aJoinerId)
{
OT_UNUSED_VARIABLE(aJoinerInfo);
mInterpreter.OutputFormat("Commissioner: Joiner ");
switch (aEvent)
{
case OT_COMMISSIONER_JOINER_START:
mInterpreter.OutputFormat("start ");
break;
case OT_COMMISSIONER_JOINER_CONNECTED:
mInterpreter.OutputFormat("connect ");
break;
case OT_COMMISSIONER_JOINER_FINALIZE:
mInterpreter.OutputFormat("finalize ");
break;
case OT_COMMISSIONER_JOINER_END:
mInterpreter.OutputFormat("end ");
break;
case OT_COMMISSIONER_JOINER_REMOVED:
mInterpreter.OutputFormat("remove ");
break;
}
if (aJoinerId != nullptr)
{
mInterpreter.OutputBytes(aJoinerId->m8, sizeof(*aJoinerId));
}
mInterpreter.OutputFormat("\r\n");
}
otError Commissioner::ProcessStop(uint8_t aArgsLength, char *aArgs[])
{
OT_UNUSED_VARIABLE(aArgsLength);
OT_UNUSED_VARIABLE(aArgs);
return otCommissionerStop(mInterpreter.mInstance);
}
otError Commissioner::ProcessState(uint8_t aArgsLength, char *aArgs[])
{
OT_UNUSED_VARIABLE(aArgsLength);
OT_UNUSED_VARIABLE(aArgs);
mInterpreter.OutputFormat("%s\r\n", StateToString(otCommissionerGetState(mInterpreter.mInstance)));
return OT_ERROR_NONE;
}
otError Commissioner::Process(uint8_t aArgsLength, char *aArgs[])
{
otError error = OT_ERROR_INVALID_COMMAND;
if (aArgsLength < 1)
{
IgnoreError(ProcessHelp(0, nullptr));
}
else
{
for (const Command &command : sCommands)
{
if (strcmp(aArgs[0], command.mName) == 0)
{
error = (this->*command.mCommand)(aArgsLength, aArgs);
break;
}
}
}
return error;
}
void Commissioner::HandleEnergyReport(uint32_t aChannelMask,
const uint8_t *aEnergyList,
uint8_t aEnergyListLength,
void * aContext)
{
static_cast<Commissioner *>(aContext)->HandleEnergyReport(aChannelMask, aEnergyList, aEnergyListLength);
}
void Commissioner::HandleEnergyReport(uint32_t aChannelMask, const uint8_t *aEnergyList, uint8_t aEnergyListLength)
{
mInterpreter.OutputFormat("Energy: %08x ", aChannelMask);
for (uint8_t i = 0; i < aEnergyListLength; i++)
{
mInterpreter.OutputFormat("%d ", static_cast<int8_t>(aEnergyList[i]));
}
mInterpreter.OutputFormat("\r\n");
}
void Commissioner::HandlePanIdConflict(uint16_t aPanId, uint32_t aChannelMask, void *aContext)
{
static_cast<Commissioner *>(aContext)->HandlePanIdConflict(aPanId, aChannelMask);
}
void Commissioner::HandlePanIdConflict(uint16_t aPanId, uint32_t aChannelMask)
{
mInterpreter.OutputFormat("Conflict: %04x, %08x\r\n", aPanId, aChannelMask);
}
} // namespace Cli
} // namespace ot
#endif // OPENTHREAD_CONFIG_COMMISSIONER_ENABLE && OPENTHREAD_FTD