From 4db030c6c9d7138e5e7f5859ecd549474e1fd717 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Fri, 2 Oct 2020 17:35:35 -0700 Subject: [PATCH] [utils] adding 'LookupTable' class and use it in CLI (#5602) This commit adds `LookupTable` module which provides helper methods to perform binary search in a generic sorted lookup table for specific entry matching a given name string. It also provides `constexpr` methods to check (at build-time) whether a lookup table array is sorted. This commit also updates CLI modules to use the `LookupTable` methods for command lookup. It also adds a unit test for the lookup table. --- Android.mk | 1 + src/cli/cli.cpp | 35 +------ src/cli/cli.hpp | 17 +--- src/cli/cli_coap.cpp | 45 ++------- src/cli/cli_coap.hpp | 27 +++++- src/cli/cli_coap_secure.cpp | 41 ++------- src/cli/cli_coap_secure.hpp | 28 +++++- src/cli/cli_commissioner.cpp | 34 ++----- src/cli/cli_commissioner.hpp | 18 +++- src/cli/cli_dataset.cpp | 42 ++------- src/cli/cli_dataset.hpp | 31 ++++++- src/cli/cli_joiner.cpp | 30 ++---- src/cli/cli_joiner.hpp | 14 ++- src/cli/cli_network_data.cpp | 36 +++----- src/cli/cli_network_data.hpp | 18 +++- src/cli/cli_udp.cpp | 35 +++---- src/cli/cli_udp.hpp | 19 +++- src/core/BUILD.gn | 3 + src/core/CMakeLists.txt | 2 + src/core/Makefile.am | 3 + src/core/api/link_raw_api.cpp | 1 - src/core/utils/lookup_table.cpp | 92 +++++++++++++++++++ src/core/utils/lookup_table.hpp | 151 +++++++++++++++++++++++++++++++ tests/unit/CMakeLists.txt | 23 +++++ tests/unit/Makefile.am | 4 + tests/unit/test_lookup_table.cpp | 112 +++++++++++++++++++++++ 26 files changed, 602 insertions(+), 260 deletions(-) create mode 100644 src/core/utils/lookup_table.cpp create mode 100644 src/core/utils/lookup_table.hpp create mode 100644 tests/unit/test_lookup_table.cpp diff --git a/Android.mk b/Android.mk index 50b829907..5d8bc454a 100644 --- a/Android.mk +++ b/Android.mk @@ -285,6 +285,7 @@ LOCAL_SRC_FILES := \ src/core/utils/child_supervision.cpp \ src/core/utils/heap.cpp \ src/core/utils/jam_detector.cpp \ + src/core/utils/lookup_table.cpp \ src/core/utils/parse_cmdline.cpp \ src/core/utils/slaac_address.cpp \ src/lib/hdlc/hdlc.cpp \ diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index e229a88dc..93ec8835a 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -328,8 +328,6 @@ otError Interpreter::ProcessHelp(uint8_t aArgsLength, char *aArgs[]) OT_UNUSED_VARIABLE(aArgsLength); OT_UNUSED_VARIABLE(aArgs); - static_assert(IsArraySorted(sCommands, OT_ARRAY_LENGTH(sCommands)), "Command list is not sorted"); - for (const Command &command : sCommands) { OutputLine(command.mName); @@ -4201,35 +4199,6 @@ otError Interpreter::ProcessDiag(uint8_t aArgsLength, char *aArgs[]) } #endif -const Interpreter::Command *Interpreter::FindCommand(const char *aName) const -{ - const Command *rval = nullptr; - uint16_t left = 0; - uint16_t right = OT_ARRAY_LENGTH(sCommands); - - while (left < right) - { - uint16_t middle = (left + right) / 2; - int compare = strcmp(aName, sCommands[middle].mName); - - if (compare == 0) - { - rval = &sCommands[middle]; - break; - } - else if (compare > 0) - { - left = middle + 1; - } - else - { - right = middle; - } - } - - return rval; -} - void Interpreter::ProcessLine(char *aBuf, uint16_t aBufLength) { char * aArgs[kMaxArgs] = {nullptr}; @@ -4250,11 +4219,11 @@ void Interpreter::ProcessLine(char *aBuf, uint16_t aBufLength) OutputLine("under diagnostics mode, execute 'diag stop' before running any other commands.")); #endif - command = FindCommand(cmdName); + command = Utils::LookupTable::Find(cmdName, sCommands); if (command != nullptr) { - OutputResult((this->*command->mCommand)(aArgsLength - 1, &aArgs[1])); + OutputResult((this->*command->mHandler)(aArgsLength - 1, &aArgs[1])); ExitNow(); } diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index 50f5665a4..6de5f5689 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -61,6 +61,7 @@ #include "common/instance.hpp" #include "common/timer.hpp" #include "net/icmp6.hpp" +#include "utils/lookup_table.hpp" namespace ot { @@ -290,10 +291,9 @@ private: struct Command { const char *mName; - otError (Interpreter::*mCommand)(uint8_t aArgsLength, char *aArgs[]); + otError (Interpreter::*mHandler)(uint8_t aArgsLength, char *aArgs[]); }; - const Command *FindCommand(const char *aName) const; otError ParsePingInterval(const char *aString, uint32_t &aInterval); static otError ParseJoinerDiscerner(char *aString, otJoinerDiscerner &aJoinerDiscerner); @@ -568,17 +568,6 @@ private: } void HandleDiscoveryRequest(const otThreadDiscoveryRequestInfo &aInfo); - constexpr static bool AreSorted(const char *aFirst, const char *aSecond) - { - return (*aFirst < *aSecond) ? true : ((*aFirst > *aSecond) ? false : AreSorted(aFirst + 1, aSecond + 1)); - } - - constexpr static bool IsArraySorted(const Interpreter::Command *aList, uint16_t aLength) - { - return (aLength <= 1) ? true - : AreSorted(aList[0].mName, aList[1].mName) && IsArraySorted(aList + 1, aLength - 1); - } - static constexpr Command sCommands[] = { #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) {"bbr", &Interpreter::ProcessBackboneRouter}, @@ -737,6 +726,8 @@ private: {"version", &Interpreter::ProcessVersion}, }; + static_assert(Utils::LookupTable::IsSorted(sCommands), "Command Table is not sorted"); + const otCliCommand *mUserCommands; uint8_t mUserCommandsLength; uint16_t mPingLength; diff --git a/src/cli/cli_coap.cpp b/src/cli/cli_coap.cpp index 945d809ec..e7c668774 100644 --- a/src/cli/cli_coap.cpp +++ b/src/cli/cli_coap.cpp @@ -43,24 +43,7 @@ namespace ot { namespace Cli { -const Coap::Command Coap::sCommands[] = { - {"help", &Coap::ProcessHelp}, -#if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE - {"cancel", &Coap::ProcessCancel}, -#endif - {"delete", &Coap::ProcessRequest}, - {"get", &Coap::ProcessRequest}, -#if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE - {"observe", &Coap::ProcessRequest}, -#endif - {"parameters", &Coap::ProcessParameters}, - {"post", &Coap::ProcessRequest}, - {"put", &Coap::ProcessRequest}, - {"resource", &Coap::ProcessResource}, - {"set", &Coap::ProcessSet}, - {"start", &Coap::ProcessStart}, - {"stop", &Coap::ProcessStop}, -}; +constexpr Coap::Command Coap::sCommands[]; Coap::Coap(Interpreter &aInterpreter) : mInterpreter(aInterpreter) @@ -513,25 +496,17 @@ exit: otError Coap::Process(uint8_t aArgsLength, char *aArgs[]) { - otError error = OT_ERROR_INVALID_COMMAND; + otError error = OT_ERROR_INVALID_ARGS; + const Command *command; - if (aArgsLength < 1) - { - IgnoreError(ProcessHelp(0, nullptr)); - error = OT_ERROR_INVALID_ARGS; - } - else - { - for (const Command &command : sCommands) - { - if (strcmp(aArgs[0], command.mName) == 0) - { - error = (this->*command.mCommand)(aArgsLength, aArgs); - break; - } - } - } + VerifyOrExit(aArgsLength != 0, IgnoreError(ProcessHelp(0, nullptr))); + command = Utils::LookupTable::Find(aArgs[0], sCommands); + VerifyOrExit(command != nullptr, error = OT_ERROR_INVALID_COMMAND); + + error = (this->*command->mHandler)(aArgsLength, aArgs); + +exit: return error; } diff --git a/src/cli/cli_coap.hpp b/src/cli/cli_coap.hpp index a406683b5..03257818b 100644 --- a/src/cli/cli_coap.hpp +++ b/src/cli/cli_coap.hpp @@ -39,6 +39,7 @@ #if OPENTHREAD_CONFIG_COAP_API_ENABLE #include "coap/coap_message.hpp" +#include "utils/lookup_table.hpp" namespace ot { namespace Cli { @@ -79,7 +80,7 @@ private: struct Command { const char *mName; - otError (Coap::*mCommand)(uint8_t aArgsLength, char *aArgs[]); + otError (Coap::*mHandler)(uint8_t aArgsLength, char *aArgs[]); }; #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE @@ -124,8 +125,28 @@ private: return mUseDefaultResponseTxParameters ? nullptr : &mResponseTxParameters; } - static const Command sCommands[]; - Interpreter & mInterpreter; + static constexpr Command sCommands[] = { +#if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE + {"cancel", &Coap::ProcessCancel}, +#endif + {"delete", &Coap::ProcessRequest}, + {"get", &Coap::ProcessRequest}, + {"help", &Coap::ProcessHelp}, +#if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE + {"observe", &Coap::ProcessRequest}, +#endif + {"parameters", &Coap::ProcessParameters}, + {"post", &Coap::ProcessRequest}, + {"put", &Coap::ProcessRequest}, + {"resource", &Coap::ProcessResource}, + {"set", &Coap::ProcessSet}, + {"start", &Coap::ProcessStart}, + {"stop", &Coap::ProcessStop}, + }; + + static_assert(Utils::LookupTable::IsSorted(sCommands), "Command Table is not sorted"); + + Interpreter &mInterpreter; bool mUseDefaultRequestTxParameters; bool mUseDefaultResponseTxParameters; diff --git a/src/cli/cli_coap_secure.cpp b/src/cli/cli_coap_secure.cpp index 0e4535fcb..7caa20e76 100644 --- a/src/cli/cli_coap_secure.cpp +++ b/src/cli/cli_coap_secure.cpp @@ -45,20 +45,7 @@ namespace ot { namespace Cli { -const CoapSecure::Command CoapSecure::sCommands[] = { - {"help", &CoapSecure::ProcessHelp}, {"connect", &CoapSecure::ProcessConnect}, - {"delete", &CoapSecure::ProcessRequest}, {"disconnect", &CoapSecure::ProcessDisconnect}, - {"get", &CoapSecure::ProcessRequest}, {"post", &CoapSecure::ProcessRequest}, - {"put", &CoapSecure::ProcessRequest}, {"resource", &CoapSecure::ProcessResource}, - {"set", &CoapSecure::ProcessSet}, {"start", &CoapSecure::ProcessStart}, - {"stop", &CoapSecure::ProcessStop}, -#ifdef MBEDTLS_KEY_EXCHANGE_PSK_ENABLED - {"psk", &CoapSecure::ProcessPsk}, -#endif -#ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED - {"x509", &CoapSecure::ProcessX509}, -#endif -}; +constexpr CoapSecure::Command CoapSecure::sCommands[]; CoapSecure::CoapSecure(Interpreter &aInterpreter) : mInterpreter(aInterpreter) @@ -410,25 +397,17 @@ otError CoapSecure::ProcessX509(uint8_t aArgsLength, char *aArgs[]) otError CoapSecure::Process(uint8_t aArgsLength, char *aArgs[]) { - otError error = OT_ERROR_INVALID_COMMAND; + otError error = OT_ERROR_INVALID_ARGS; + const Command *command; - if (aArgsLength < 1) - { - IgnoreError(ProcessHelp(0, nullptr)); - error = OT_ERROR_INVALID_ARGS; - } - else - { - for (const Command &command : sCommands) - { - if (strcmp(aArgs[0], command.mName) == 0) - { - error = (this->*command.mCommand)(aArgsLength, aArgs); - break; - } - } - } + VerifyOrExit(aArgsLength != 0, IgnoreError(ProcessHelp(0, nullptr))); + command = Utils::LookupTable::Find(aArgs[0], sCommands); + VerifyOrExit(command != nullptr, error = OT_ERROR_INVALID_COMMAND); + + error = (this->*command->mHandler)(aArgsLength, aArgs); + +exit: return error; } diff --git a/src/cli/cli_coap_secure.hpp b/src/cli/cli_coap_secure.hpp index e81e24f02..18f0d1fe5 100644 --- a/src/cli/cli_coap_secure.hpp +++ b/src/cli/cli_coap_secure.hpp @@ -40,6 +40,7 @@ #include "coap/coap_message.hpp" #include "coap/coap_secure.hpp" +#include "utils/lookup_table.hpp" #ifndef CLI_COAP_SECURE_USE_COAP_DEFAULT_HANDLER #define CLI_COAP_SECURE_USE_COAP_DEFAULT_HANDLER 0 @@ -86,7 +87,7 @@ private: struct Command { const char *mName; - otError (CoapSecure::*mCommand)(uint8_t aArgsLength, char *aArgs[]); + otError (CoapSecure::*mHandler)(uint8_t aArgsLength, char *aArgs[]); }; void PrintPayload(otMessage *aMessage) const; @@ -118,8 +119,29 @@ private: static void HandleConnected(bool aConnected, void *aContext); void HandleConnected(bool aConnected); - static const Command sCommands[]; - Interpreter & mInterpreter; + static constexpr Command sCommands[] = { + {"connect", &CoapSecure::ProcessConnect}, + {"delete", &CoapSecure::ProcessRequest}, + {"disconnect", &CoapSecure::ProcessDisconnect}, + {"get", &CoapSecure::ProcessRequest}, + {"help", &CoapSecure::ProcessHelp}, + {"post", &CoapSecure::ProcessRequest}, +#ifdef MBEDTLS_KEY_EXCHANGE_PSK_ENABLED + {"psk", &CoapSecure::ProcessPsk}, +#endif + {"put", &CoapSecure::ProcessRequest}, + {"resource", &CoapSecure::ProcessResource}, + {"set", &CoapSecure::ProcessSet}, + {"start", &CoapSecure::ProcessStart}, + {"stop", &CoapSecure::ProcessStop}, +#ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + {"x509", &CoapSecure::ProcessX509}, +#endif + }; + + static_assert(Utils::LookupTable::IsSorted(sCommands), "Command Table is not sorted"); + + Interpreter &mInterpreter; otCoapResource mResource; char mUriPath[kMaxUriLength]; diff --git a/src/cli/cli_commissioner.cpp b/src/cli/cli_commissioner.cpp index 0b6d3881e..59725a69f 100644 --- a/src/cli/cli_commissioner.cpp +++ b/src/cli/cli_commissioner.cpp @@ -40,14 +40,7 @@ namespace ot { namespace Cli { -const 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}, -}; +constexpr Commissioner::Command Commissioner::sCommands[]; otError Commissioner::ProcessHelp(uint8_t aArgsLength, char *aArgs[]) { @@ -436,24 +429,17 @@ otError Commissioner::ProcessState(uint8_t aArgsLength, char *aArgs[]) otError Commissioner::Process(uint8_t aArgsLength, char *aArgs[]) { - otError error = OT_ERROR_INVALID_COMMAND; + otError error = OT_ERROR_INVALID_COMMAND; + const Command *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; - } - } - } + VerifyOrExit(aArgsLength != 0, IgnoreError(ProcessHelp(0, nullptr))); + command = Utils::LookupTable::Find(aArgs[0], sCommands); + VerifyOrExit(command != nullptr, OT_NOOP); + + error = (this->*command->mHandler)(aArgsLength, aArgs); + +exit: return error; } diff --git a/src/cli/cli_commissioner.hpp b/src/cli/cli_commissioner.hpp index 51ef2d8e0..dcdb6a7e3 100644 --- a/src/cli/cli_commissioner.hpp +++ b/src/cli/cli_commissioner.hpp @@ -38,6 +38,8 @@ #include +#include "utils/lookup_table.hpp" + #if OPENTHREAD_CONFIG_COMMISSIONER_ENABLE && OPENTHREAD_FTD namespace ot { @@ -81,7 +83,7 @@ private: struct Command { const char *mName; - otError (Commissioner::*mCommand)(uint8_t aArgsLength, char *aArgs[]); + otError (Commissioner::*mHandler)(uint8_t aArgsLength, char *aArgs[]); }; otError ProcessHelp(uint8_t aArgsLength, char *aArgs[]); @@ -119,8 +121,18 @@ private: static const char *StateToString(otCommissionerState aState); - static const Command sCommands[]; - Interpreter & mInterpreter; + static constexpr Command sCommands[] = { + {"announce", &Commissioner::ProcessAnnounce}, {"energy", &Commissioner::ProcessEnergy}, + {"help", &Commissioner::ProcessHelp}, {"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}, + }; + + static_assert(Utils::LookupTable::IsSorted(sCommands), "Command Table is not sorted"); + + Interpreter &mInterpreter; }; } // namespace Cli diff --git a/src/cli/cli_dataset.cpp b/src/cli/cli_dataset.cpp index 5f83236f2..1196adcad 100644 --- a/src/cli/cli_dataset.cpp +++ b/src/cli/cli_dataset.cpp @@ -44,31 +44,8 @@ namespace ot { namespace Cli { -const Dataset::Command Dataset::sCommands[] = { - {"help", &Dataset::ProcessHelp}, - {"active", &Dataset::ProcessActive}, - {"activetimestamp", &Dataset::ProcessActiveTimestamp}, - {"channel", &Dataset::ProcessChannel}, - {"channelmask", &Dataset::ProcessChannelMask}, - {"clear", &Dataset::ProcessClear}, - {"commit", &Dataset::ProcessCommit}, - {"delay", &Dataset::ProcessDelay}, - {"extpanid", &Dataset::ProcessExtPanId}, - {"init", &Dataset::ProcessInit}, - {"masterkey", &Dataset::ProcessMasterKey}, - {"meshlocalprefix", &Dataset::ProcessMeshLocalPrefix}, - {"mgmtgetcommand", &Dataset::ProcessMgmtGetCommand}, - {"mgmtsetcommand", &Dataset::ProcessMgmtSetCommand}, - {"networkname", &Dataset::ProcessNetworkName}, - {"panid", &Dataset::ProcessPanId}, - {"pending", &Dataset::ProcessPending}, - {"pendingtimestamp", &Dataset::ProcessPendingTimestamp}, - {"pskc", &Dataset::ProcessPskc}, - {"securitypolicy", &Dataset::ProcessSecurityPolicy}, - {"set", &Dataset::ProcessSet}, -}; - -otOperationalDataset Dataset::sDataset; +constexpr Dataset::Command Dataset::sCommands[]; +otOperationalDataset Dataset::sDataset; otError Dataset::Print(otOperationalDataset &aDataset) { @@ -175,21 +152,18 @@ otError Dataset::Print(otOperationalDataset &aDataset) otError Dataset::Process(uint8_t aArgsLength, char *aArgs[]) { - otError error = OT_ERROR_INVALID_COMMAND; + otError error = OT_ERROR_INVALID_COMMAND; + const Command *command; if (aArgsLength == 0) { ExitNow(error = Print(sDataset)); } - for (const Command &command : sCommands) - { - if (strcmp(aArgs[0], command.mName) == 0) - { - error = (this->*command.mCommand)(aArgsLength - 1, aArgs + 1); - break; - } - } + command = Utils::LookupTable::Find(aArgs[0], sCommands); + VerifyOrExit(command != nullptr, OT_NOOP); + + error = (this->*command->mHandler)(aArgsLength - 1, aArgs + 1); exit: return error; diff --git a/src/cli/cli_dataset.hpp b/src/cli/cli_dataset.hpp index a4bb5669d..f3f7e5de5 100644 --- a/src/cli/cli_dataset.hpp +++ b/src/cli/cli_dataset.hpp @@ -40,6 +40,8 @@ #include +#include "utils/lookup_table.hpp" + namespace ot { namespace Cli { @@ -70,7 +72,7 @@ private: struct Command { const char *mName; - otError (Dataset::*mCommand)(uint8_t aArgsLength, char *aArgs[]); + otError (Dataset::*mHandler)(uint8_t aArgsLength, char *aArgs[]); }; otError Print(otOperationalDataset &aDataset); @@ -97,9 +99,34 @@ private: otError ProcessSecurityPolicy(uint8_t aArgsLength, char *aArgs[]); otError ProcessSet(uint8_t aArgsLength, char *aArgs[]); + static constexpr Command sCommands[] = { + {"active", &Dataset::ProcessActive}, + {"activetimestamp", &Dataset::ProcessActiveTimestamp}, + {"channel", &Dataset::ProcessChannel}, + {"channelmask", &Dataset::ProcessChannelMask}, + {"clear", &Dataset::ProcessClear}, + {"commit", &Dataset::ProcessCommit}, + {"delay", &Dataset::ProcessDelay}, + {"extpanid", &Dataset::ProcessExtPanId}, + {"help", &Dataset::ProcessHelp}, + {"init", &Dataset::ProcessInit}, + {"masterkey", &Dataset::ProcessMasterKey}, + {"meshlocalprefix", &Dataset::ProcessMeshLocalPrefix}, + {"mgmtgetcommand", &Dataset::ProcessMgmtGetCommand}, + {"mgmtsetcommand", &Dataset::ProcessMgmtSetCommand}, + {"networkname", &Dataset::ProcessNetworkName}, + {"panid", &Dataset::ProcessPanId}, + {"pending", &Dataset::ProcessPending}, + {"pendingtimestamp", &Dataset::ProcessPendingTimestamp}, + {"pskc", &Dataset::ProcessPskc}, + {"securitypolicy", &Dataset::ProcessSecurityPolicy}, + {"set", &Dataset::ProcessSet}, + }; + + static_assert(Utils::LookupTable::IsSorted(sCommands), "Command Table is not sorted"); + Interpreter &mInterpreter; - static const Command sCommands[]; static otOperationalDataset sDataset; }; diff --git a/src/cli/cli_joiner.cpp b/src/cli/cli_joiner.cpp index 729b0615d..e59892aa3 100644 --- a/src/cli/cli_joiner.cpp +++ b/src/cli/cli_joiner.cpp @@ -42,10 +42,7 @@ namespace ot { namespace Cli { -const Joiner::Command Joiner::sCommands[] = { - {"discerner", &Joiner::ProcessDiscerner}, {"help", &Joiner::ProcessHelp}, {"id", &Joiner::ProcessId}, - {"start", &Joiner::ProcessStart}, {"stop", &Joiner::ProcessStop}, -}; +constexpr Joiner::Command Joiner::sCommands[]; otError Joiner::ProcessDiscerner(uint8_t aArgsLength, char *aArgs[]) { @@ -139,24 +136,17 @@ otError Joiner::ProcessStop(uint8_t aArgsLength, char *aArgs[]) otError Joiner::Process(uint8_t aArgsLength, char *aArgs[]) { - otError error = OT_ERROR_INVALID_COMMAND; + otError error = OT_ERROR_INVALID_COMMAND; + const Command *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; - } - } - } + VerifyOrExit(aArgsLength != 0, IgnoreError(ProcessHelp(0, nullptr))); + command = Utils::LookupTable::Find(aArgs[0], sCommands); + VerifyOrExit(command != nullptr, OT_NOOP); + + error = (this->*command->mHandler)(aArgsLength, aArgs); + +exit: return error; } diff --git a/src/cli/cli_joiner.hpp b/src/cli/cli_joiner.hpp index 5ff0eea74..9944c16d5 100644 --- a/src/cli/cli_joiner.hpp +++ b/src/cli/cli_joiner.hpp @@ -38,6 +38,8 @@ #include +#include "utils/lookup_table.hpp" + #if OPENTHREAD_CONFIG_JOINER_ENABLE namespace ot { @@ -76,7 +78,7 @@ private: struct Command { const char *mName; - otError (Joiner::*mCommand)(uint8_t aArgsLength, char *aArgs[]); + otError (Joiner::*mHandler)(uint8_t aArgsLength, char *aArgs[]); }; otError ProcessDiscerner(uint8_t aArgsLength, char *aArgs[]); @@ -88,8 +90,14 @@ private: static void HandleCallback(otError aError, void *aContext); void HandleCallback(otError aError); - static const Command sCommands[]; - Interpreter & mInterpreter; + static constexpr Command sCommands[] = { + {"discerner", &Joiner::ProcessDiscerner}, {"help", &Joiner::ProcessHelp}, {"id", &Joiner::ProcessId}, + {"start", &Joiner::ProcessStart}, {"stop", &Joiner::ProcessStop}, + }; + + static_assert(Utils::LookupTable::IsSorted(sCommands), "Command Table is not sorted"); + + Interpreter &mInterpreter; }; } // namespace Cli diff --git a/src/cli/cli_network_data.cpp b/src/cli/cli_network_data.cpp index 6de7141a8..01622f7b3 100644 --- a/src/cli/cli_network_data.cpp +++ b/src/cli/cli_network_data.cpp @@ -44,14 +44,7 @@ using ot::Encoding::BigEndian::HostSwap16; namespace ot { namespace Cli { -const NetworkData::Command NetworkData::sCommands[] = { - {"help", &NetworkData::ProcessHelp}, -#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE || OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE - {"register", &NetworkData::ProcessRegister}, -#endif - {"show", &NetworkData::ProcessShow}, - {"steeringdata", &NetworkData::ProcessSteeringData}, -}; +constexpr NetworkData::Command NetworkData::sCommands[]; NetworkData::NetworkData(Interpreter &aInterpreter) : mInterpreter(aInterpreter) @@ -318,24 +311,17 @@ otError NetworkData::ProcessShow(uint8_t aArgsLength, char *aArgs[]) otError NetworkData::Process(uint8_t aArgsLength, char *aArgs[]) { - otError error = OT_ERROR_INVALID_COMMAND; + otError error = OT_ERROR_INVALID_COMMAND; + const Command *command; - if (aArgsLength < 1) - { - IgnoreError(ProcessHelp(0, nullptr)); - error = OT_ERROR_INVALID_ARGS; - } - else - { - for (const Command &command : sCommands) - { - if (strcmp(aArgs[0], command.mName) == 0) - { - error = (this->*command.mCommand)(aArgsLength - 1, aArgs + 1); - break; - } - } - } + VerifyOrExit(aArgsLength != 0, IgnoreError(ProcessHelp(0, nullptr))); + + command = Utils::LookupTable::Find(aArgs[0], sCommands); + VerifyOrExit(command != nullptr, OT_NOOP); + + error = (this->*command->mHandler)(aArgsLength - 1, aArgs + 1); + +exit: return error; } diff --git a/src/cli/cli_network_data.hpp b/src/cli/cli_network_data.hpp index 8b0b4761f..6e37a2d69 100644 --- a/src/cli/cli_network_data.hpp +++ b/src/cli/cli_network_data.hpp @@ -38,6 +38,8 @@ #include +#include "utils/lookup_table.hpp" + namespace ot { namespace Cli { @@ -95,7 +97,7 @@ private: struct Command { const char *mName; - otError (NetworkData::*mCommand)(uint8_t aArgsLength, char *aArgs[]); + otError (NetworkData::*mHandler)(uint8_t aArgsLength, char *aArgs[]); }; otError ProcessHelp(uint8_t aArgsLength, char *aArgs[]); @@ -110,8 +112,18 @@ private: void OutputRoutes(void); void OutputServices(void); - static const Command sCommands[]; - Interpreter & mInterpreter; + static constexpr Command sCommands[] = { + {"help", &NetworkData::ProcessHelp}, +#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE || OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE + {"register", &NetworkData::ProcessRegister}, +#endif + {"show", &NetworkData::ProcessShow}, + {"steeringdata", &NetworkData::ProcessSteeringData}, + }; + + static_assert(Utils::LookupTable::IsSorted(sCommands), "Command Table is not sorted"); + + Interpreter &mInterpreter; }; } // namespace Cli diff --git a/src/cli/cli_udp.cpp b/src/cli/cli_udp.cpp index b7a22cb92..fd4f819fd 100644 --- a/src/cli/cli_udp.cpp +++ b/src/cli/cli_udp.cpp @@ -44,13 +44,7 @@ using ot::Encoding::BigEndian::HostSwap16; namespace ot { namespace Cli { -const UdpExample::Command UdpExample::sCommands[] = {{"help", &UdpExample::ProcessHelp}, - {"bind", &UdpExample::ProcessBind}, - {"close", &UdpExample::ProcessClose}, - {"connect", &UdpExample::ProcessConnect}, - {"linksecurity", &UdpExample::ProcessLinkSecurity}, - {"open", &UdpExample::ProcessOpen}, - {"send", &UdpExample::ProcessSend}}; +constexpr UdpExample::Command UdpExample::sCommands[]; UdpExample::UdpExample(Interpreter &aInterpreter) : mInterpreter(aInterpreter) @@ -289,24 +283,17 @@ exit: otError UdpExample::Process(uint8_t aArgsLength, char *aArgs[]) { - otError error = OT_ERROR_INVALID_COMMAND; + otError error = OT_ERROR_INVALID_ARGS; + const Command *command; - if (aArgsLength < 1) - { - IgnoreError(ProcessHelp(0, nullptr)); - error = OT_ERROR_INVALID_ARGS; - } - else - { - for (const Command &command : sCommands) - { - if (strcmp(aArgs[0], command.mName) == 0) - { - error = (this->*command.mCommand)(aArgsLength - 1, aArgs + 1); - break; - } - } - } + VerifyOrExit(aArgsLength != 0, IgnoreError(ProcessHelp(0, nullptr))); + + command = Utils::LookupTable::Find(aArgs[0], sCommands); + VerifyOrExit(command != nullptr, error = OT_ERROR_INVALID_COMMAND); + + error = (this->*command->mHandler)(aArgsLength - 1, aArgs + 1); + +exit: return error; } diff --git a/src/cli/cli_udp.hpp b/src/cli/cli_udp.hpp index bc49c4d3e..12c556dad 100644 --- a/src/cli/cli_udp.hpp +++ b/src/cli/cli_udp.hpp @@ -38,6 +38,8 @@ #include +#include "utils/lookup_table.hpp" + namespace ot { namespace Cli { @@ -71,7 +73,7 @@ private: struct Command { const char *mName; - otError (UdpExample::*mCommand)(uint8_t aArgsLength, char *aArgs[]); + otError (UdpExample::*mHandler)(uint8_t aArgsLength, char *aArgs[]); }; enum PayloadType @@ -93,8 +95,19 @@ private: static void HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo); void HandleUdpReceive(otMessage *aMessage, const otMessageInfo *aMessageInfo); - static const Command sCommands[]; - Interpreter & mInterpreter; + static constexpr Command sCommands[] = { + {"bind", &UdpExample::ProcessBind}, + {"close", &UdpExample::ProcessClose}, + {"connect", &UdpExample::ProcessConnect}, + {"help", &UdpExample::ProcessHelp}, + {"linksecurity", &UdpExample::ProcessLinkSecurity}, + {"open", &UdpExample::ProcessOpen}, + {"send", &UdpExample::ProcessSend}, + }; + + static_assert(Utils::LookupTable::IsSorted(sCommands), "Command Table is not sorted"); + + Interpreter &mInterpreter; bool mLinkSecurityEnabled; otUdpSocket mSocket; diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index 965eac595..4579f936b 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -571,6 +571,8 @@ openthread_core_files = [ "utils/heap.hpp", "utils/jam_detector.cpp", "utils/jam_detector.hpp", + "utils/lookup_table.cpp", + "utils/lookup_table.hpp", "utils/otns.cpp", "utils/otns.hpp", "utils/parse_cmdline.cpp", @@ -604,6 +606,7 @@ openthread_radio_sources = [ "radio/radio_callbacks.cpp", "radio/radio_platform.cpp", "thread/link_quality.cpp", + "utils/lookup_table.cpp", "utils/parse_cmdline.cpp", ] diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index aff9e1f42..7fb38e816 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -216,6 +216,7 @@ set(COMMON_SOURCES utils/flash.cpp utils/heap.cpp utils/jam_detector.cpp + utils/lookup_table.cpp utils/otns.cpp utils/parse_cmdline.cpp utils/slaac_address.cpp @@ -258,6 +259,7 @@ target_sources(openthread-radio PRIVATE radio/radio_callbacks.cpp radio/radio_platform.cpp thread/link_quality.cpp + utils/lookup_table.cpp utils/parse_cmdline.cpp ) diff --git a/src/core/Makefile.am b/src/core/Makefile.am index eabfd6103..941d144e6 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -258,6 +258,7 @@ SOURCES_COMMON = \ utils/flash.cpp \ utils/heap.cpp \ utils/jam_detector.cpp \ + utils/lookup_table.cpp \ utils/otns.cpp \ utils/parse_cmdline.cpp \ utils/slaac_address.cpp \ @@ -293,6 +294,7 @@ libopenthread_radio_a_SOURCES = \ radio/radio_callbacks.cpp \ radio/radio_platform.cpp \ thread/link_quality.cpp \ + utils/lookup_table.cpp \ utils/parse_cmdline.cpp \ $(NULL) @@ -488,6 +490,7 @@ HEADERS_COMMON = \ utils/flash.hpp \ utils/heap.hpp \ utils/jam_detector.hpp \ + utils/lookup_table.hpp \ utils/otns.hpp \ utils/parse_cmdline.hpp \ utils/slaac_address.hpp \ diff --git a/src/core/api/link_raw_api.cpp b/src/core/api/link_raw_api.cpp index 98d67d063..9bf138ea4 100644 --- a/src/core/api/link_raw_api.cpp +++ b/src/core/api/link_raw_api.cpp @@ -44,7 +44,6 @@ #include "common/locator-getters.hpp" #include "common/random.hpp" #include "mac/mac_frame.hpp" -#include "utils/parse_cmdline.hpp" #if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE diff --git a/src/core/utils/lookup_table.cpp b/src/core/utils/lookup_table.cpp new file mode 100644 index 000000000..e4e8dee6b --- /dev/null +++ b/src/core/utils/lookup_table.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2020, 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 the lookup table (binary search) functionality. + */ + +#include + +#include "lookup_table.hpp" + +#include "common/code_utils.hpp" + +namespace ot { +namespace Utils { + +const void *LookupTable::Find(const char *aName, + const void *aTable, + uint16_t aLength, + uint16_t aTableEntrySize, + NameGetter aNameGetter) +{ + const void *entry; + uint16_t left = 0; + uint16_t right = aLength; + + while (left < right) + { + uint16_t middle = (left + right) / 2; + int compare; + + // Note that `aTable` array entry type is not known here and + // only its size is given as `aTableEntrySize`. Based on this, + // we can calculate the pointer to the table entry at any index + // (such as `[middle]`) which is then passed to the given + // `aNameGetter` function which knows how to cast the void + // pointer to proper entry type and get the enclosed `mName` + // field. This model keeps the implementation generic and + // re-usable allowing it to be used with any entry type. + + entry = reinterpret_cast(aTable) + aTableEntrySize * middle; + + compare = strcmp(aName, aNameGetter(entry)); + + if (compare == 0) + { + ExitNow(); + } + else if (compare > 0) + { + left = middle + 1; + } + else + { + right = middle; + } + } + + entry = nullptr; + +exit: + return entry; +} + +} // namespace Utils +} // namespace ot diff --git a/src/core/utils/lookup_table.hpp b/src/core/utils/lookup_table.hpp new file mode 100644 index 000000000..bedf13a3e --- /dev/null +++ b/src/core/utils/lookup_table.hpp @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2020, 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 includes definitions for lookup table (binary search) functionality. + */ + +#ifndef LOOKUP_TABLE_HPP_ +#define LOOKUP_TABLE_HPP_ + +#include +#include + +namespace ot { +namespace Utils { + +/** + * This class implements lookup table (using binary search) functionality. + * + */ +class LookupTable +{ +public: + /** + * This struct represents an example of a lookup table entry. + * + */ + struct Entry + { + /** + * This constructor initializes the entry with a given name. + * + * @param[in] aName The null-terminated name string with which to initialize the entry. + * + */ + constexpr Entry(const char *aName) + : mName(aName) + { + } + + const char *const mName; + }; + + /** + * This template method indicates whether a given entry table array is sorted based on entry's name string and in + * alphabetical order and contains not duplicate entries. + * + * This method is `constexpr` and is intended for use in `static_assert`s to verify that a `constexpr` lookup table + * array is sorted. + * + * @tparam EntryType The table entry type. The `EntryType` MUST provide `mName` member variable as a `const + * char *`. For example, this can be realized by `EntryType` being a subclass of `Entry`. + * @tparam kLength The array length (number of entries in the array). + * + * @note In the common use of this method as `IsSorted(sTable)` where sTable` is a fixed size array, the template + * types/parameters do not need to be explicitly specified and can be deduced from the passed-in argument. + * + * @param[in] aTable A reference to an array of `kLength` entries on type `EntryType` + * + * @retval TRUE If the entries in @p aTable are sorted (alphabetical order). + * @retval FALSE If the entries in @p aTable are not sorted. + * + */ + template static constexpr bool IsSorted(const EntryType (&aTable)[kLength]) + { + return IsSorted(&aTable[0], kLength); + } + + /** + * This template method performs binary search in a given sorted table array to find an entry matching a given name. + * + * @note This method requires the array to be sorted, otherwise its behavior is undefined. + * + * @tparam EntryType The table entry type. The `EntryType` MUST provide `mName` member variable as a `const + * char *`. For example, this can be realized by `EntryType` being a subclass of `Entry`. + * @tparam kLength The array length (number of entries in the array). + * + * @note In the common use of this method as `Find(name, sTable)` where sTable` is a fixed size array, the template + * types/parameters do not need to be explicitly specified and can be deduced from the passed-in argument. + * + * @param[in] aName A name string to search for within the table. + * @param[in] aTable A reference to an array of `kLength` entries on type `EntryType` + * + * @returns A pointer to the entry in the table if a match is found, otherwise nullptr (no match in table). + * + */ + template + static const EntryType *Find(const char *aName, const EntryType (&aTable)[kLength]) + { + return reinterpret_cast( + Find(aName, &aTable[0], kLength, sizeof(aTable[0]), GetName)); + } + +private: + typedef const char *(&NameGetter)(const void *aPointer); + + template static const char *GetName(const void *aPointer) + { + return reinterpret_cast(aPointer)->mName; + } + + template static constexpr bool IsSorted(const EntryType *aTable, uint16_t aLength) + { + return (aLength <= 1) ? true + : AreInOrder(aTable[0].mName, aTable[1].mName) && IsSorted(aTable + 1, aLength - 1); + } + + constexpr static bool AreInOrder(const char *aFirst, const char *aSecond) + { + return (*aFirst < *aSecond) + ? true + : ((*aFirst > *aSecond) || (*aFirst == '\0') ? false : AreInOrder(aFirst + 1, aSecond + 1)); + } + + static const void *Find(const char *aName, + const void *aTable, + uint16_t aLength, + uint16_t aTableEntrySize, + NameGetter aNameGetter); +}; + +} // namespace Utils +} // namespace ot + +#endif // LOOKUP_TABLE_HPP_ diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 53a07bf1e..bd4e248e5 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -271,6 +271,28 @@ target_link_libraries(test-linked-list add_test(NAME test-linked-list COMMAND test-linked-list) +add_executable(test-lookup-table + ${COMMON_SOURCES} + test_lookup_table.cpp +) + +target_include_directories(test-lookup-table + PRIVATE + ${COMMON_INCLUDES} +) + +target_compile_options(test-lookup-table + PRIVATE + ${COMMON_COMPILE_OPTIONS} +) + +target_link_libraries(test-lookup-table + PRIVATE + ${COMMON_LIBS} +) + +add_test(NAME test-lookup-table COMMAND test-lookup-table) + add_executable(test-lowpan ${COMMON_SOURCES} test_lowpan.cpp @@ -592,6 +614,7 @@ set_target_properties( test-ip6-address test-link-quality test-linked-list + test-lookup-table test-lowpan test-mac-frame test-message diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am index 11fc3a414..4c1971ed4 100644 --- a/tests/unit/Makefile.am +++ b/tests/unit/Makefile.am @@ -116,6 +116,7 @@ check_PROGRAMS += \ test-ip6-address \ test-link-quality \ test-linked-list \ + test-lookup-table \ test-lowpan \ test-mac-frame \ test-message \ @@ -203,6 +204,9 @@ test_link_quality_SOURCES = $(COMMON_SOURCES) test_link_quality.cpp test_linked_list_LDADD = $(COMMON_LDADD) test_linked_list_SOURCES = $(COMMON_SOURCES) test_linked_list.cpp +test_lookup_table_LDADD = $(COMMON_LDADD) +test_lookup_table_SOURCES = $(COMMON_SOURCES) test_lookup_table.cpp + test_lowpan_LDADD = $(COMMON_LDADD) test_lowpan_SOURCES = $(COMMON_SOURCES) test_lowpan.cpp diff --git a/tests/unit/test_lookup_table.cpp b/tests/unit/test_lookup_table.cpp new file mode 100644 index 000000000..17fb658b3 --- /dev/null +++ b/tests/unit/test_lookup_table.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2020, 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. + */ + +#include + +#include "test_platform.h" + +#include + +#include "common/instance.hpp" +#include "utils/lookup_table.hpp" + +#include "test_util.h" + +typedef ot::Utils::LookupTable::Entry Entry; + +struct TableEntryBase +{ + constexpr TableEntryBase(uint8_t aValue) + : mValue(aValue) + { + } + + uint8_t mValue; +}; + +struct TableEntry : public TableEntryBase, public Entry +{ + constexpr TableEntry(const char *aName, uint8_t aValue) + : TableEntryBase(aValue) + , Entry(aName) + , mUint16(0xabba) + , mUint8(aValue) + { + } + + uint16_t mUint16; + uint8_t mUint8; +}; + +void TestLookupTable(void) +{ + enum : uint16_t + { + kMaxNameSize = 30, + }; + + constexpr TableEntry kTable[] = { + {"arkham city", 9}, {"arkham knight", 7}, {"bloodborne", 10}, {"god of war", 10}, {"horizon", 9}, + {"infamous", 7}, {"last guardian", 7}, {"last of us", 11}, {"last of us part 2", 8}, {"mass effect", 8}, + {"sekiro", 10}, {"tomb raider", 9}, {"uncharted", 9}, + }; + + constexpr Entry kUnsortedTable[] = {{"z"}, {"a"}, {"b"}}; + constexpr Entry kDuplicateEntryTable[] = {"duplicate", "duplicate"}; + + static_assert(ot::Utils::LookupTable::IsSorted(kTable), "LookupTable::IsSorted() failed"); + static_assert(!ot::Utils::LookupTable::IsSorted(kUnsortedTable), + "LookupTable::IsSorted() failed for unsorted table"); + static_assert(!ot::Utils::LookupTable::IsSorted(kDuplicateEntryTable), + "LookupTable::IsSorted() failed for table with duplicate entries"); + + for (const TableEntry &tableEntry : kTable) + { + const TableEntry *entry; + char name[kMaxNameSize]; + + strcpy(name, tableEntry.mName); + + entry = ot::Utils::LookupTable::Find(name, kTable); + VerifyOrQuit(entry == &tableEntry, "LookupTable::Find() failed"); + + name[strlen(name) - 1] = '\0'; + entry = ot::Utils::LookupTable::Find(name, kTable); + VerifyOrQuit(entry == nullptr, "LookupTable::Find() failed with non-matching name"); + } + + VerifyOrQuit(ot::Utils::LookupTable::Find("dragon age", kTable) == nullptr, + "LookupTable::Find() failed with non exiting mathc"); +} + +int main(void) +{ + TestLookupTable(); + printf("All tests passed\n"); + return 0; +}