1 /*
2  *  Copyright (c) 2024, The OpenThread Authors.
3  *  All rights reserved.
4  *
5  *  Redistribution and use in source and binary forms, with or without
6  *  modification, are permitted provided that the following conditions are met:
7  *  1. Redistributions of source code must retain the above copyright
8  *     notice, this list of conditions and the following disclaimer.
9  *  2. Redistributions in binary form must reproduce the above copyright
10  *     notice, this list of conditions and the following disclaimer in the
11  *     documentation and/or other materials provided with the distribution.
12  *  3. Neither the name of the copyright holder nor the
13  *     names of its contributors may be used to endorse or promote products
14  *     derived from this software without specific prior written permission.
15  *
16  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  *  POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #include "rcp_caps_diag.hpp"
30 
31 #if OPENTHREAD_POSIX_CONFIG_RCP_CAPS_DIAG_ENABLE
32 namespace ot {
33 namespace Posix {
34 
35 #define SPINEL_ENTRY(aCategory, aCommand, aKey)                                      \
36     {                                                                                \
37         aCategory, aCommand, aKey, &RcpCapsDiag::HandleSpinelCommand<aCommand, aKey> \
38     }
39 
HandleSpinelCommand(void)40 template <> otError RcpCapsDiag::HandleSpinelCommand<SPINEL_CMD_PROP_VALUE_GET, SPINEL_PROP_PHY_CCA_THRESHOLD>(void)
41 {
42     int8_t ccaThreshold;
43 
44     return mRadioSpinel.GetCcaEnergyDetectThreshold(ccaThreshold);
45 }
46 
HandleSpinelCommand(void)47 template <> otError RcpCapsDiag::HandleSpinelCommand<SPINEL_CMD_PROP_VALUE_SET, SPINEL_PROP_PHY_CHAN>(void)
48 {
49     static constexpr uint8_t kPhyChannel = 22;
50 
51     return mRadioSpinel.Set(SPINEL_PROP_PHY_CHAN, SPINEL_DATATYPE_UINT8_S, kPhyChannel);
52 }
53 
HandleSpinelCommand(void)54 template <> otError RcpCapsDiag::HandleSpinelCommand<SPINEL_CMD_PROP_VALUE_GET, SPINEL_PROP_CAPS>(void)
55 {
56     static constexpr uint8_t kCapsBufferSize = 100;
57     uint8_t                  capsBuffer[kCapsBufferSize];
58     spinel_size_t            capsLength = sizeof(capsBuffer);
59 
60     return mRadioSpinel.Get(SPINEL_PROP_CAPS, SPINEL_DATATYPE_DATA_S, capsBuffer, &capsLength);
61 }
62 
HandleSpinelCommand(void)63 template <> otError RcpCapsDiag::HandleSpinelCommand<SPINEL_CMD_PROP_VALUE_SET, SPINEL_PROP_RCP_ENH_ACK_PROBING>(void)
64 {
65     uint16_t     shortAddress = 0x1122;
66     otExtAddress extAddress   = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
67     uint8_t      flags        = SPINEL_THREAD_LINK_METRIC_PDU_COUNT | SPINEL_THREAD_LINK_METRIC_LQI |
68                     SPINEL_THREAD_LINK_METRIC_LINK_MARGIN | SPINEL_THREAD_LINK_METRIC_RSSI;
69 
70     return mRadioSpinel.Set(SPINEL_PROP_RCP_ENH_ACK_PROBING,
71                             SPINEL_DATATYPE_UINT16_S SPINEL_DATATYPE_EUI64_S SPINEL_DATATYPE_UINT8_S, shortAddress,
72                             extAddress.m8, flags);
73 }
74 
75 const struct RcpCapsDiag::SpinelEntry RcpCapsDiag::sSpinelEntries[] = {
76     //  Basic Spinel commands
77     SPINEL_ENTRY(kCategoryBasic, SPINEL_CMD_PROP_VALUE_GET, SPINEL_PROP_CAPS),
78 
79     // Thread Version >= 1.1
80     SPINEL_ENTRY(kCategoryThread1_1, SPINEL_CMD_PROP_VALUE_SET, SPINEL_PROP_PHY_CHAN),
81 
82     // Thread Version >= 1.2
83     SPINEL_ENTRY(kCategoryThread1_2, SPINEL_CMD_PROP_VALUE_SET, SPINEL_PROP_RCP_ENH_ACK_PROBING),
84 
85     // Optional Spinel commands
86     SPINEL_ENTRY(kCategoryOptional, SPINEL_CMD_PROP_VALUE_GET, SPINEL_PROP_PHY_CCA_THRESHOLD),
87 };
88 
DiagProcess(char * aArgs[],uint8_t aArgsLength,char * aOutput,size_t aOutputMaxLen)89 otError RcpCapsDiag::DiagProcess(char *aArgs[], uint8_t aArgsLength, char *aOutput, size_t aOutputMaxLen)
90 {
91     otError error = OT_ERROR_NONE;
92 
93     VerifyOrExit(aArgsLength == 2, error = OT_ERROR_INVALID_ARGS);
94 
95     mOutputStart = aOutput;
96     mOutputEnd   = aOutput + aOutputMaxLen;
97 
98     if (strcmp(aArgs[1], "spinel") == 0)
99     {
100         ProcessSpinel();
101     }
102     else
103     {
104         error = OT_ERROR_INVALID_COMMAND;
105     }
106 
107     mOutputStart = nullptr;
108     mOutputEnd   = nullptr;
109 
110 exit:
111     return error;
112 }
113 
ProcessSpinel(void)114 void RcpCapsDiag::ProcessSpinel(void)
115 {
116     for (uint8_t i = 0; i < kNumCategories; i++)
117     {
118         TestSpinelCommands(static_cast<Category>(i));
119     }
120 }
121 
TestSpinelCommands(Category aCategory)122 void RcpCapsDiag::TestSpinelCommands(Category aCategory)
123 {
124     otError error;
125 
126     Output("\r\n%s :\r\n", CategoryToString(aCategory));
127 
128     for (const SpinelEntry &entry : sSpinelEntries)
129     {
130         if (entry.mCategory != aCategory)
131         {
132             continue;
133         }
134 
135         error = (this->*entry.mHandler)();
136         OutputResult(entry, error);
137     }
138 }
139 
OutputResult(const SpinelEntry & aEntry,otError error)140 void RcpCapsDiag::OutputResult(const SpinelEntry &aEntry, otError error)
141 {
142     static constexpr uint8_t  kSpaceLength            = 1;
143     static constexpr uint8_t  kMaxCommandStringLength = 20;
144     static constexpr uint8_t  kMaxKeyStringLength     = 35;
145     static constexpr uint16_t kMaxLength              = kMaxCommandStringLength + kMaxKeyStringLength + kSpaceLength;
146     static const char         kPadding[]              = "----------------------------------------------------------";
147     const char               *commandString           = spinel_command_to_cstr(aEntry.mCommand);
148     const char               *keyString               = spinel_prop_key_to_cstr(aEntry.mKey);
149     uint16_t actualLength  = static_cast<uint16_t>(strlen(commandString) + strlen(keyString) + kSpaceLength);
150     uint16_t paddingOffset = (actualLength > kMaxLength) ? kMaxLength : actualLength;
151 
152     static_assert(kMaxLength < sizeof(kPadding), "Padding bytes are too short");
153 
154     Output("%.*s %.*s %s %s\r\n", kMaxCommandStringLength, commandString, kMaxKeyStringLength, keyString,
155            &kPadding[paddingOffset], otThreadErrorToString(error));
156 }
157 
Output(const char * aFormat,...)158 void RcpCapsDiag::Output(const char *aFormat, ...)
159 {
160     va_list args;
161 
162     va_start(args, aFormat);
163 
164     if ((mOutputStart != nullptr) && (mOutputEnd != nullptr) && (mOutputStart < mOutputEnd))
165     {
166         mOutputStart += vsnprintf(mOutputStart, static_cast<size_t>(mOutputEnd - mOutputStart), aFormat, args);
167     }
168 
169     va_end(args);
170 }
171 
CategoryToString(Category aCategory)172 const char *RcpCapsDiag::CategoryToString(Category aCategory)
173 {
174     static const char *const kCategoryStrings[] = {
175         "Basic",                 // (0) kCategoryBasic
176         "Thread Version >= 1.1", // (1) kCategoryThread1_1
177         "Thread Version >= 1.2", // (2) kCategoryThread1_2
178         "Optional",              // (3) kCategoryOptional
179     };
180 
181     static_assert(kCategoryBasic == 0, "kCategoryBasic value is incorrect");
182     static_assert(kCategoryThread1_1 == 1, "kCategoryThread1_1 value is incorrect");
183     static_assert(kCategoryThread1_2 == 2, "kCategoryThread1_2 value is incorrect");
184     static_assert(kCategoryOptional == 3, "kCategoryOptional value is incorrect");
185 
186     return (aCategory < OT_ARRAY_LENGTH(kCategoryStrings)) ? kCategoryStrings[aCategory] : "invalid";
187 }
188 
189 } // namespace Posix
190 } // namespace ot
191 #endif // OPENTHREAD_POSIX_CONFIG_RCP_CAPS_DIAG_ENABLE
192