• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Copyright (c) 2021, 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 /**
30  * @file
31  *   This file contains implementation of the CLI output module.
32  */
33 
34 #include "cli_output.hpp"
35 
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 
40 #if OPENTHREAD_FTD || OPENTHREAD_MTD
41 #include <openthread/dns.h>
42 #endif
43 #include <openthread/logging.h>
44 
45 #include "common/string.hpp"
46 
47 namespace ot {
48 namespace Cli {
49 
50 const char OutputBase::kUnknownString[] = "unknown";
51 
Output(otInstance * aInstance,otCliOutputCallback aCallback,void * aCallbackContext)52 Output::Output(otInstance *aInstance, otCliOutputCallback aCallback, void *aCallbackContext)
53     : mInstance(aInstance)
54     , mCallback(aCallback)
55     , mCallbackContext(aCallbackContext)
56 #if OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_ENABLE
57     , mOutputLength(0)
58     , mEmittingCommandOutput(true)
59 #endif
60 {
61 }
62 
OutputFormat(const char * aFormat,...)63 void Output::OutputFormat(const char *aFormat, ...)
64 {
65     va_list args;
66 
67     va_start(args, aFormat);
68     OutputFormatV(aFormat, args);
69     va_end(args);
70 }
71 
OutputFormat(uint8_t aIndentSize,const char * aFormat,...)72 void Output::OutputFormat(uint8_t aIndentSize, const char *aFormat, ...)
73 {
74     va_list args;
75 
76     OutputSpaces(aIndentSize);
77 
78     va_start(args, aFormat);
79     OutputFormatV(aFormat, args);
80     va_end(args);
81 }
82 
OutputLine(const char * aFormat,...)83 void Output::OutputLine(const char *aFormat, ...)
84 {
85     va_list args;
86 
87     va_start(args, aFormat);
88     OutputFormatV(aFormat, args);
89     va_end(args);
90 
91     OutputFormat("\r\n");
92 }
93 
OutputLine(uint8_t aIndentSize,const char * aFormat,...)94 void Output::OutputLine(uint8_t aIndentSize, const char *aFormat, ...)
95 {
96     va_list args;
97 
98     OutputSpaces(aIndentSize);
99 
100     va_start(args, aFormat);
101     OutputFormatV(aFormat, args);
102     va_end(args);
103 
104     OutputFormat("\r\n");
105 }
106 
OutputSpaces(uint8_t aCount)107 void Output::OutputSpaces(uint8_t aCount)
108 {
109     char format[sizeof("%256s")];
110 
111     snprintf(format, sizeof(format), "%%%us", aCount);
112 
113     OutputFormat(format, "");
114 }
115 
OutputBytes(const uint8_t * aBytes,uint16_t aLength)116 void Output::OutputBytes(const uint8_t *aBytes, uint16_t aLength)
117 {
118     for (uint16_t i = 0; i < aLength; i++)
119     {
120         OutputFormat("%02x", aBytes[i]);
121     }
122 }
123 
OutputBytesLine(const uint8_t * aBytes,uint16_t aLength)124 void Output::OutputBytesLine(const uint8_t *aBytes, uint16_t aLength)
125 {
126     OutputBytes(aBytes, aLength);
127     OutputLine("");
128 }
129 
OutputEnabledDisabledStatus(bool aEnabled)130 void Output::OutputEnabledDisabledStatus(bool aEnabled)
131 {
132     OutputLine(aEnabled ? "Enabled" : "Disabled");
133 }
134 
135 #if OPENTHREAD_FTD || OPENTHREAD_MTD
136 
OutputIp6Address(const otIp6Address & aAddress)137 void Output::OutputIp6Address(const otIp6Address &aAddress)
138 {
139     char string[OT_IP6_ADDRESS_STRING_SIZE];
140 
141     otIp6AddressToString(&aAddress, string, sizeof(string));
142 
143     return OutputFormat("%s", string);
144 }
145 
OutputIp6AddressLine(const otIp6Address & aAddress)146 void Output::OutputIp6AddressLine(const otIp6Address &aAddress)
147 {
148     OutputIp6Address(aAddress);
149     OutputLine("");
150 }
151 
OutputIp6Prefix(const otIp6Prefix & aPrefix)152 void Output::OutputIp6Prefix(const otIp6Prefix &aPrefix)
153 {
154     char string[OT_IP6_PREFIX_STRING_SIZE];
155 
156     otIp6PrefixToString(&aPrefix, string, sizeof(string));
157 
158     OutputFormat("%s", string);
159 }
160 
OutputIp6PrefixLine(const otIp6Prefix & aPrefix)161 void Output::OutputIp6PrefixLine(const otIp6Prefix &aPrefix)
162 {
163     OutputIp6Prefix(aPrefix);
164     OutputLine("");
165 }
166 
OutputIp6Prefix(const otIp6NetworkPrefix & aPrefix)167 void Output::OutputIp6Prefix(const otIp6NetworkPrefix &aPrefix)
168 {
169     OutputFormat("%x:%x:%x:%x::/64", (aPrefix.m8[0] << 8) | aPrefix.m8[1], (aPrefix.m8[2] << 8) | aPrefix.m8[3],
170                  (aPrefix.m8[4] << 8) | aPrefix.m8[5], (aPrefix.m8[6] << 8) | aPrefix.m8[7]);
171 }
172 
OutputIp6PrefixLine(const otIp6NetworkPrefix & aPrefix)173 void Output::OutputIp6PrefixLine(const otIp6NetworkPrefix &aPrefix)
174 {
175     OutputIp6Prefix(aPrefix);
176     OutputLine("");
177 }
178 
OutputSockAddr(const otSockAddr & aSockAddr)179 void Output::OutputSockAddr(const otSockAddr &aSockAddr)
180 {
181     char string[OT_IP6_SOCK_ADDR_STRING_SIZE];
182 
183     otIp6SockAddrToString(&aSockAddr, string, sizeof(string));
184 
185     return OutputFormat("%s", string);
186 }
187 
OutputSockAddrLine(const otSockAddr & aSockAddr)188 void Output::OutputSockAddrLine(const otSockAddr &aSockAddr)
189 {
190     OutputSockAddr(aSockAddr);
191     OutputLine("");
192 }
193 
OutputDnsTxtData(const uint8_t * aTxtData,uint16_t aTxtDataLength)194 void Output::OutputDnsTxtData(const uint8_t *aTxtData, uint16_t aTxtDataLength)
195 {
196     otDnsTxtEntry         entry;
197     otDnsTxtEntryIterator iterator;
198     bool                  isFirst = true;
199 
200     otDnsInitTxtEntryIterator(&iterator, aTxtData, aTxtDataLength);
201 
202     OutputFormat("[");
203 
204     while (otDnsGetNextTxtEntry(&iterator, &entry) == OT_ERROR_NONE)
205     {
206         if (!isFirst)
207         {
208             OutputFormat(", ");
209         }
210 
211         if (entry.mKey == nullptr)
212         {
213             // A null `mKey` indicates that the key in the entry is
214             // longer than the recommended max key length, so the entry
215             // could not be parsed. In this case, the whole entry is
216             // returned encoded in `mValue`.
217 
218             OutputFormat("[");
219             OutputBytes(entry.mValue, entry.mValueLength);
220             OutputFormat("]");
221         }
222         else
223         {
224             OutputFormat("%s", entry.mKey);
225 
226             if (entry.mValue != nullptr)
227             {
228                 OutputFormat("=");
229                 OutputBytes(entry.mValue, entry.mValueLength);
230             }
231         }
232 
233         isFirst = false;
234     }
235 
236     OutputFormat("]");
237 }
238 #endif // OPENTHREAD_FTD || OPENTHREAD_MTD
239 
OutputFormatV(const char * aFormat,va_list aArguments)240 void Output::OutputFormatV(const char *aFormat, va_list aArguments)
241 {
242 #if OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_ENABLE
243     va_list args;
244     int     charsWritten;
245     bool    truncated = false;
246 
247     va_copy(args, aArguments);
248 #endif
249 
250     mCallback(mCallbackContext, aFormat, aArguments);
251 
252 #if OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_ENABLE
253     VerifyOrExit(mEmittingCommandOutput);
254 
255     charsWritten = vsnprintf(&mOutputString[mOutputLength], sizeof(mOutputString) - mOutputLength, aFormat, args);
256 
257     VerifyOrExit(charsWritten >= 0, mOutputLength = 0);
258 
259     if (static_cast<uint32_t>(charsWritten) >= sizeof(mOutputString) - mOutputLength)
260     {
261         truncated     = true;
262         mOutputLength = sizeof(mOutputString) - 1;
263     }
264     else
265     {
266         mOutputLength += charsWritten;
267     }
268 
269     while (true)
270     {
271         char *lineEnd = strchr(mOutputString, '\r');
272 
273         if (lineEnd == nullptr)
274         {
275             break;
276         }
277 
278         *lineEnd = '\0';
279 
280         if (lineEnd > mOutputString)
281         {
282             otLogCli(OT_LOG_LEVEL_DEBG, "Output: %s", mOutputString);
283         }
284 
285         lineEnd++;
286 
287         while ((*lineEnd == '\n') || (*lineEnd == '\r'))
288         {
289             lineEnd++;
290         }
291 
292         // Example of the pointers and lengths.
293         //
294         // - mOutputString = "hi\r\nmore"
295         // - mOutputLength = 8
296         // - lineEnd       = &mOutputString[4]
297         //
298         //
299         //   0    1    2    3    4    5    6    7    8    9
300         // +----+----+----+----+----+----+----+----+----+---
301         // | h  | i  | \r | \n | m  | o  | r  | e  | \0 |
302         // +----+----+----+----+----+----+----+----+----+---
303         //                       ^                   ^
304         //                       |                   |
305         //                    lineEnd    mOutputString[mOutputLength]
306         //
307         //
308         // New length is `&mOutputString[8] - &mOutputString[4] -> 4`.
309         //
310         // We move (newLen + 1 = 5) chars from `lineEnd` to start of
311         // `mOutputString` which will include the `\0` char.
312         //
313         // If `lineEnd` and `mOutputString[mOutputLength]` are the same
314         // the code works correctly as well  (new length set to zero and
315         // the `\0` is copied).
316 
317         mOutputLength = static_cast<uint16_t>(&mOutputString[mOutputLength] - lineEnd);
318         memmove(mOutputString, lineEnd, mOutputLength + 1);
319     }
320 
321     if (truncated)
322     {
323         otLogCli(OT_LOG_LEVEL_DEBG, "Output: %s ...", mOutputString);
324         mOutputLength = 0;
325     }
326 
327 exit:
328     va_end(args);
329 #endif // OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_ENABLE
330 }
331 
332 #if OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_ENABLE
LogInput(const Arg * aArgs)333 void Output::LogInput(const Arg *aArgs)
334 {
335     String<kInputOutputLogStringSize> inputString;
336 
337     for (bool isFirst = true; !aArgs->IsEmpty(); aArgs++, isFirst = false)
338     {
339         inputString.Append(isFirst ? "%s" : " %s", aArgs->GetCString());
340     }
341 
342     otLogCli(OT_LOG_LEVEL_DEBG, "Input: %s", inputString.AsCString());
343 }
344 #endif
345 
OutputTableHeader(uint8_t aNumColumns,const char * const aTitles[],const uint8_t aWidths[])346 void Output::OutputTableHeader(uint8_t aNumColumns, const char *const aTitles[], const uint8_t aWidths[])
347 {
348     for (uint8_t index = 0; index < aNumColumns; index++)
349     {
350         const char *title       = aTitles[index];
351         uint8_t     width       = aWidths[index];
352         size_t      titleLength = strlen(title);
353 
354         if (titleLength + 2 <= width)
355         {
356             // `title` fits in column width so we write it with extra space
357             // at beginning and end ("| Title    |").
358 
359             OutputFormat("| %*s", -static_cast<int>(width - 1), title);
360         }
361         else
362         {
363             // Use narrow style (no space at beginning) and write as many
364             // chars from `title` as it can fit in the given column width
365             // ("|Title|").
366 
367             OutputFormat("|%*.*s", -static_cast<int>(width), width, title);
368         }
369     }
370 
371     OutputLine("|");
372     OutputTableSeparator(aNumColumns, aWidths);
373 }
374 
OutputTableSeparator(uint8_t aNumColumns,const uint8_t aWidths[])375 void Output::OutputTableSeparator(uint8_t aNumColumns, const uint8_t aWidths[])
376 {
377     for (uint8_t index = 0; index < aNumColumns; index++)
378     {
379         OutputFormat("+");
380 
381         for (uint8_t width = aWidths[index]; width != 0; width--)
382         {
383             OutputFormat("-");
384         }
385     }
386 
387     OutputLine("+");
388 }
389 
390 } // namespace Cli
391 } // namespace ot
392