mirror of
https://github.com/espressif/openthread.git
synced 2026-08-01 08:37:47 +00:00
[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:
@@ -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
@@ -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);
|
||||
|
||||
@@ -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>()`
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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:]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user