Coverage Report

Created: 2024-02-25 06:30

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