[cli] disallow concurrent commands (#6695)

This commit disallows concurrent commands.

With this commit, the CLI only execute a new command after it complete
the previous command. CLI can also prompt properly after the command
execution is done.

Other fixes and enhancements:
- Fixes premature command prompt
- Add ping async command for ping in async mode: output Done
  immediately but print ping responses later on.
- Fixes networkdiagnostic get outputs multiple Done by always waiting
  for 5 seconds.
This commit is contained in:
Simon Lin
2021-09-09 20:46:43 -07:00
committed by GitHub
parent 9e631d816e
commit db2e313ef0
9 changed files with 108 additions and 48 deletions
+14 -15
View File
@@ -134,9 +134,9 @@ static otError ProcessCommand(void);
static void ReceiveTask(const uint8_t *aBuf, uint16_t aBufLength)
{
static const char sEraseString[] = {'\b', ' ', '\b'};
static const char CRNL[] = {'\r', '\n'};
static const char sCommandPrompt[] = {'>', ' '};
static const char sEraseString[] = {'\b', ' ', '\b'};
static const char CRNL[] = {'\r', '\n'};
static uint8_t sLastChar = '\0';
const uint8_t * end;
end = aBuf + aBufLength;
@@ -145,17 +145,18 @@ static void ReceiveTask(const uint8_t *aBuf, uint16_t aBufLength)
{
switch (*aBuf)
{
case '\r':
case '\n':
Output(CRNL, sizeof(CRNL));
if (sRxLength > 0)
if (sLastChar == '\r')
{
sRxBuffer[sRxLength] = '\0';
IgnoreError(ProcessCommand());
break;
}
Output(sCommandPrompt, sizeof(sCommandPrompt));
OT_FALL_THROUGH;
case '\r':
Output(CRNL, sizeof(CRNL));
sRxBuffer[sRxLength] = '\0';
IgnoreError(ProcessCommand());
break;
#if OPENTHREAD_POSIX && !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
@@ -187,6 +188,8 @@ static void ReceiveTask(const uint8_t *aBuf, uint16_t aBufLength)
break;
}
sLastChar = *aBuf;
}
}
@@ -194,16 +197,12 @@ static otError ProcessCommand(void)
{
otError error = OT_ERROR_NONE;
while (sRxBuffer[sRxLength - 1] == '\n' || sRxBuffer[sRxLength - 1] == '\r')
while (sRxLength > 0 && (sRxBuffer[sRxLength - 1] == '\n' || sRxBuffer[sRxLength - 1] == '\r'))
{
sRxBuffer[--sRxLength] = '\0';
}
if (sRxLength > 0)
{
otCliInputLine(sRxBuffer);
}
otCliInputLine(sRxBuffer);
sRxLength = 0;
return error;
+73 -25
View File
@@ -104,6 +104,7 @@ Interpreter::Interpreter(Instance *aInstance, otCliOutputCallback aCallback, voi
, mOutputContext(aContext)
, mUserCommands(nullptr)
, mUserCommandsLength(0)
, mCommandIsPending(false)
#if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE
, mSntpQueryingInProgress(false)
#endif
@@ -113,6 +114,7 @@ Interpreter::Interpreter(Instance *aInstance, otCliOutputCallback aCallback, voi
#if OPENTHREAD_CONFIG_TCP_ENABLE && OPENTHREAD_CONFIG_CLI_TCP_ENABLE
, mTcp(*this)
#endif
, mTimer(*aInstance, HandleTimer, this)
#if OPENTHREAD_CONFIG_COAP_API_ENABLE
, mCoap(*this)
#endif
@@ -142,22 +144,31 @@ Interpreter::Interpreter(Instance *aInstance, otCliOutputCallback aCallback, voi
#if OPENTHREAD_FTD
otThreadSetDiscoveryRequestCallback(mInstance, &Interpreter::HandleDiscoveryRequest, this);
#endif
OutputPrompt();
}
void Interpreter::OutputResult(otError aError)
{
switch (aError)
OT_ASSERT(mCommandIsPending);
VerifyOrExit(aError != OT_ERROR_PENDING);
if (aError == OT_ERROR_NONE)
{
case OT_ERROR_NONE:
OutputLine("Done");
break;
case OT_ERROR_PENDING:
break;
default:
}
else
{
OutputLine("Error %d: %s", aError, otThreadErrorToString(aError));
}
mCommandIsPending = false;
mTimer.Stop();
OutputPrompt();
exit:
return;
}
void Interpreter::OutputBytes(const uint8_t *aBytes, uint16_t aLength)
@@ -3186,19 +3197,29 @@ void Interpreter::HandlePingStatistics(const otPingSenderStatistics *aStatistics
}
OutputLine("");
OutputResult(OT_ERROR_NONE);
if (!mPingIsAsync)
{
OutputResult(OT_ERROR_NONE);
}
}
otError Interpreter::ProcessPing(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
otPingSenderConfig config;
bool async = false;
if (aArgs[0] == "stop")
{
otPingSenderStop(mInstance);
ExitNow();
}
else if (aArgs[0] == "async")
{
async = true;
aArgs++;
}
memset(&config, 0, sizeof(config));
@@ -3265,7 +3286,14 @@ otError Interpreter::ProcessPing(Arg aArgs[])
config.mStatisticsCallback = Interpreter::HandlePingStatistics;
config.mCallbackContext = this;
error = otPingSenderPing(mInstance, &config);
SuccessOrExit(error = otPingSenderPing(mInstance, &config));
mPingIsAsync = async;
if (!async)
{
error = kErrorPending;
}
exit:
return error;
@@ -4669,21 +4697,18 @@ void Interpreter::ProcessLine(char *aBuf)
OT_ASSERT(aBuf != nullptr);
// Ignore the command if another command is pending.
VerifyOrExit(!mCommandIsPending, args[0].Clear());
mCommandIsPending = true;
VerifyOrExit(StringLength(aBuf, kMaxLineLength) <= kMaxLineLength - 1, error = OT_ERROR_PARSE);
#if OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_ENABLE
otLogNoteCli("Input: %s", aBuf);
#endif
error = Utils::CmdLineParser::ParseCmd(aBuf, args);
if (error != OT_ERROR_NONE)
{
OutputLine("Error: too many args (max %d)", kMaxArgs);
ExitNow();
}
VerifyOrExit(!args[0].IsEmpty());
SuccessOrExit(error = Utils::CmdLineParser::ParseCmd(aBuf, args, kMaxArgs));
VerifyOrExit(!args[0].IsEmpty(), mCommandIsPending = false);
#if OPENTHREAD_CONFIG_DIAG_ENABLE
if (otDiagIsEnabled(mInstance) && (args[0] != "diag"))
@@ -4709,6 +4734,10 @@ exit:
{
OutputResult(error);
}
else if (!mCommandIsPending)
{
OutputPrompt();
}
}
otError Interpreter::ProcessUserCommands(Arg aArgs[])
@@ -4766,6 +4795,7 @@ otError Interpreter::ProcessNetworkDiagnostic(Arg aArgs[])
{
SuccessOrExit(error = otThreadSendDiagnosticGet(mInstance, &address, tlvTypes, count,
&Interpreter::HandleDiagnosticGetResponse, this));
SetCommandTimeout(kNetworkDiagnosticTimeoutMsecs);
error = OT_ERROR_PENDING;
}
else if (aArgs[0] == "reset")
@@ -4897,13 +4927,8 @@ void Interpreter::HandleDiagnosticGetResponse(otError aError,
}
}
if (aError == OT_ERROR_NOT_FOUND)
{
aError = OT_ERROR_NONE;
}
exit:
OutputResult(aError);
return;
}
void Interpreter::OutputMode(uint8_t aIndentSize, const otLinkModeConfig &aMode)
@@ -5152,6 +5177,29 @@ void Interpreter::Initialize(otInstance *aInstance, otCliOutputCallback aCallbac
Interpreter::sInterpreter = new (&sInterpreterRaw) Interpreter(instance, aCallback, aContext);
}
void Interpreter::OutputPrompt(void)
{
static const char sPrompt[] = "> ";
OutputFormat("%s", sPrompt);
}
void Interpreter::HandleTimer(Timer &aTimer)
{
static_cast<Interpreter *>(static_cast<TimerMilliContext &>(aTimer).GetContext())->HandleTimer();
}
void Interpreter::HandleTimer(void)
{
OutputResult(kErrorNone);
}
void Interpreter::SetCommandTimeout(uint32_t aTimeoutMilli)
{
OT_ASSERT(mCommandIsPending);
mTimer.Start(aTimeoutMilli);
}
extern "C" void otCliInit(otInstance *aInstance, otCliOutputCallback aCallback, void *aContext)
{
Interpreter::Initialize(aInstance, aCallback, aContext);
+13
View File
@@ -336,6 +336,8 @@ private:
kMaxLineLength = OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH,
};
static constexpr uint32_t kNetworkDiagnosticTimeoutMsecs = 5000;
struct Command
{
const char *mName;
@@ -432,6 +434,7 @@ private:
OutputTableSeperator(kTableNumColumns, aWidths);
}
void OutputPrompt(void);
#if OPENTHREAD_CONFIG_PING_SENDER_ENABLE
otError ParsePingInterval(const Arg &aArg, uint32_t &aInterval);
#endif
@@ -772,6 +775,10 @@ private:
bool IsLogging(void) const { return mIsLogging; }
void SetIsLogging(bool aIsLogging) { mIsLogging = aIsLogging; }
#endif
void SetCommandTimeout(uint32_t aTimeoutMilli);
static void HandleTimer(Timer &aTimer);
void HandleTimer(void);
static constexpr Command sCommands[] = {
#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE
@@ -969,6 +976,7 @@ private:
const otCliCommand *mUserCommands;
uint8_t mUserCommandsLength;
void * mUserCommandsContext;
bool mCommandIsPending;
#if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE
bool mSntpQueryingInProgress;
#endif
@@ -981,6 +989,8 @@ private:
TcpExample mTcp;
#endif
TimerMilliContext mTimer;
#if OPENTHREAD_CONFIG_COAP_API_ENABLE
Coap mCoap;
#endif
@@ -1014,6 +1024,9 @@ private:
uint16_t mOutputLength;
bool mIsLogging;
#endif
#if OPENTHREAD_CONFIG_PING_SENDER_ENABLE
bool mPingIsAsync;
#endif
};
// Specializations of `FormatStringFor<ValueType>()`
+1 -1
View File
@@ -97,7 +97,7 @@ PingSender::PingSender(Instance &aInstance)
Error PingSender::Ping(const Config &aConfig)
{
Error error = kErrorPending;
Error error = kErrorNone;
VerifyOrExit(!mTimer.IsRunning(), error = kErrorBusy);
+4 -4
View File
@@ -56,8 +56,6 @@
#include "openthread-core-config.h"
#include "platform-posix.h"
static const char sPrompt[] = "> ";
static void InputCallback(char *aLine)
{
if (aLine != nullptr)
@@ -65,8 +63,8 @@ static void InputCallback(char *aLine)
if (aLine[0] != '\0')
{
add_history(aLine);
otCliInputLine(aLine);
}
otCliInputLine(aLine);
free(aLine);
}
else
@@ -90,7 +88,9 @@ extern "C" void otAppCliInit(otInstance *aInstance)
rl_set_screen_size(0, OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH);
rl_callback_handler_install(sPrompt, InputCallback);
rl_callback_handler_install("", InputCallback);
rl_already_prompted = true;
otCliInit(aInstance, OutputCallback, nullptr);
}
-2
View File
@@ -49,7 +49,6 @@
#include "platform-posix.h"
namespace {
constexpr char sPrompt[] = "> ";
int OutputCallback(void *aContext, const char *aFormat, va_list aArguments)
{
@@ -93,7 +92,6 @@ extern "C" void otAppCliProcess(const otSysMainloopContext *aMainloop)
if (fgets(buffer, sizeof(buffer), stdin) != nullptr)
{
otCliInputLine(buffer);
dprintf(STDOUT_FILENO, "%s", sPrompt);
}
else
{
-1
View File
@@ -311,7 +311,6 @@ void Daemon::Process(const otSysMainloopContext &aContext)
buffer[rval] = '\0';
otLogInfoPlat("> %s", reinterpret_cast<const char *>(buffer));
otCliInputLine(reinterpret_cast<char *>(buffer));
otCliOutputFormat("> ");
}
else
{
+1
View File
@@ -58,6 +58,7 @@ proc wait_for {command success {failure {[\r\n]FAILURE_NOT_EXPECTED[\r\n]}}} {
}
proc expect_line {line} {
set timeout 10
expect -re "\[\r\n \]($line)(?=\[\r\n>\])"
return $expect_out(1,string)
}
+2
View File
@@ -180,6 +180,8 @@ class OtCliCommandRunner(OTCommandHandler):
def __otcli_read_routine(self):
while not self.__should_close.isSet():
line = self.__otcli.readline()
logging.debug('%s: %r', self.__otcli, line)
if line.startswith('> '):
line = line[2:]