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