1 /*
2 * Copyright (c) 2017, 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 implements a simple CLI for the CoAP service.
32 */
33
34 #include "cli_coap.hpp"
35
36 #if OPENTHREAD_CONFIG_COAP_API_ENABLE
37
38 #include <openthread/random_noncrypto.h>
39
40 #include <ctype.h>
41
42 #include "cli/cli.hpp"
43
44 namespace ot {
45 namespace Cli {
46
Coap(otInstance * aInstance,OutputImplementer & aOutputImplementer)47 Coap::Coap(otInstance *aInstance, OutputImplementer &aOutputImplementer)
48 : Utils(aInstance, aOutputImplementer)
49 , mUseDefaultRequestTxParameters(true)
50 , mUseDefaultResponseTxParameters(true)
51 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
52 , mObserveSerial(0)
53 , mRequestTokenLength(0)
54 , mSubscriberTokenLength(0)
55 , mSubscriberConfirmableNotifications(false)
56 #endif
57 #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
58 , mBlockCount(1)
59 #endif
60 {
61 ClearAllBytes(mResource);
62 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
63 ClearAllBytes(mRequestAddr);
64 ClearAllBytes(mSubscriberSock);
65 ClearAllBytes(mRequestToken);
66 ClearAllBytes(mSubscriberToken);
67 ClearAllBytes(mRequestUri);
68 #endif
69 ClearAllBytes(mUriPath);
70 strncpy(mResourceContent, "0", sizeof(mResourceContent));
71 mResourceContent[sizeof(mResourceContent) - 1] = '\0';
72 }
73
74 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
CancelResourceSubscription(void)75 otError Coap::CancelResourceSubscription(void)
76 {
77 otError error = OT_ERROR_NONE;
78 otMessage *message = nullptr;
79 otMessageInfo messageInfo;
80
81 ClearAllBytes(messageInfo);
82 messageInfo.mPeerAddr = mRequestAddr;
83 messageInfo.mPeerPort = OT_DEFAULT_COAP_PORT;
84
85 VerifyOrExit(mRequestTokenLength != 0, error = OT_ERROR_INVALID_STATE);
86
87 message = otCoapNewMessage(GetInstancePtr(), nullptr);
88 VerifyOrExit(message != nullptr, error = OT_ERROR_NO_BUFS);
89
90 otCoapMessageInit(message, OT_COAP_TYPE_CONFIRMABLE, OT_COAP_CODE_GET);
91
92 SuccessOrExit(error = otCoapMessageSetToken(message, mRequestToken, mRequestTokenLength));
93 SuccessOrExit(error = otCoapMessageAppendObserveOption(message, 1));
94 SuccessOrExit(error = otCoapMessageAppendUriPathOptions(message, mRequestUri));
95 SuccessOrExit(error = otCoapSendRequest(GetInstancePtr(), message, &messageInfo, &Coap::HandleResponse, this));
96
97 ClearAllBytes(mRequestAddr);
98 ClearAllBytes(mRequestUri);
99 mRequestTokenLength = 0;
100
101 exit:
102
103 if ((error != OT_ERROR_NONE) && (message != nullptr))
104 {
105 otMessageFree(message);
106 }
107
108 return error;
109 }
110
CancelSubscriber(void)111 void Coap::CancelSubscriber(void)
112 {
113 ClearAllBytes(mSubscriberSock);
114 mSubscriberTokenLength = 0;
115 }
116 #endif // OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
117
PrintPayload(otMessage * aMessage)118 void Coap::PrintPayload(otMessage *aMessage)
119 {
120 uint8_t buf[kMaxBufferSize];
121 uint16_t bytesToPrint;
122 uint16_t bytesPrinted = 0;
123 uint16_t length = otMessageGetLength(aMessage) - otMessageGetOffset(aMessage);
124
125 if (length > 0)
126 {
127 OutputFormat(" with payload: ");
128
129 while (length > 0)
130 {
131 bytesToPrint = Min(length, static_cast<uint16_t>(sizeof(buf)));
132 otMessageRead(aMessage, otMessageGetOffset(aMessage) + bytesPrinted, buf, bytesToPrint);
133
134 OutputBytes(buf, static_cast<uint8_t>(bytesToPrint));
135
136 length -= bytesToPrint;
137 bytesPrinted += bytesToPrint;
138 }
139 }
140
141 OutputNewLine();
142 }
143
144 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
145 /**
146 * @cli coap cancel
147 * @code
148 * coap cancel
149 * Done
150 * @endcode
151 * @par
152 * Cancels an existing observation subscription to a remote resource on the CoAP server.
153 * @note This command is available only when `OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE` is set.
154 * @csa{coap observe}
155 */
Process(Arg aArgs[])156 template <> otError Coap::Process<Cmd("cancel")>(Arg aArgs[])
157 {
158 OT_UNUSED_VARIABLE(aArgs);
159
160 return CancelResourceSubscription();
161 }
162 #endif
163
164 /**
165 * @cli coap resource (get,set)
166 * @code
167 * coap resource test-resource
168 * Done
169 * @endcode
170 * @code
171 * coap resource
172 * test-resource
173 * Done
174 * @endcode
175 * @cparam coap resource [@ca{uri-path}]
176 * @par
177 * Gets or sets the URI path of the CoAP server resource.
178 * @sa otCoapAddResource
179 * @sa otCoapAddBlockWiseResource
180 */
Process(Arg aArgs[])181 template <> otError Coap::Process<Cmd("resource")>(Arg aArgs[])
182 {
183 otError error = OT_ERROR_NONE;
184
185 if (!aArgs[0].IsEmpty())
186 {
187 VerifyOrExit(aArgs[0].GetLength() < kMaxUriLength, error = OT_ERROR_INVALID_ARGS);
188
189 mResource.mUriPath = mUriPath;
190 mResource.mContext = this;
191 mResource.mHandler = &Coap::HandleRequest;
192
193 #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
194 mResource.mReceiveHook = &Coap::BlockwiseReceiveHook;
195 mResource.mTransmitHook = &Coap::BlockwiseTransmitHook;
196
197 if (!aArgs[1].IsEmpty())
198 {
199 SuccessOrExit(error = aArgs[1].ParseAsUint32(mBlockCount));
200 }
201 #endif
202
203 strncpy(mUriPath, aArgs[0].GetCString(), sizeof(mUriPath) - 1);
204
205 #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
206 otCoapAddBlockWiseResource(GetInstancePtr(), &mResource);
207 #else
208 otCoapAddResource(GetInstancePtr(), &mResource);
209 #endif
210 }
211 else
212 {
213 OutputLine("%s", mResource.mUriPath != nullptr ? mResource.mUriPath : "");
214 }
215
216 exit:
217 return error;
218 }
219
220 /**
221 * @cli coap set
222 * @code
223 * coap set Testing123
224 * Done
225 * @endcode
226 * @cparam coap set @ca{new-content}
227 * @par
228 * Sets the content sent by the resource on the CoAP server.
229 * If a CoAP client is observing the resource, a notification is sent to that client.
230 * @csa{coap observe}
231 * @sa otCoapMessageInit
232 * @sa otCoapNewMessage
233 */
Process(Arg aArgs[])234 template <> otError Coap::Process<Cmd("set")>(Arg aArgs[])
235 {
236 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
237 otMessage *notificationMessage = nullptr;
238 otMessageInfo messageInfo;
239 #endif
240 otError error = OT_ERROR_NONE;
241
242 if (!aArgs[0].IsEmpty())
243 {
244 VerifyOrExit(aArgs[0].GetLength() < sizeof(mResourceContent), error = OT_ERROR_INVALID_ARGS);
245 strncpy(mResourceContent, aArgs[0].GetCString(), sizeof(mResourceContent));
246 mResourceContent[sizeof(mResourceContent) - 1] = '\0';
247
248 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
249 if (mSubscriberTokenLength > 0)
250 {
251 // Notify the subscriber
252 ClearAllBytes(messageInfo);
253 messageInfo.mPeerAddr = mSubscriberSock.mAddress;
254 messageInfo.mPeerPort = mSubscriberSock.mPort;
255
256 OutputFormat("sending coap notification to ");
257 OutputIp6AddressLine(mSubscriberSock.mAddress);
258
259 notificationMessage = otCoapNewMessage(GetInstancePtr(), nullptr);
260 VerifyOrExit(notificationMessage != nullptr, error = OT_ERROR_NO_BUFS);
261
262 otCoapMessageInit(
263 notificationMessage,
264 ((mSubscriberConfirmableNotifications) ? OT_COAP_TYPE_CONFIRMABLE : OT_COAP_TYPE_NON_CONFIRMABLE),
265 OT_COAP_CODE_CONTENT);
266
267 SuccessOrExit(error = otCoapMessageSetToken(notificationMessage, mSubscriberToken, mSubscriberTokenLength));
268 SuccessOrExit(error = otCoapMessageAppendObserveOption(notificationMessage, mObserveSerial++));
269 SuccessOrExit(error = otCoapMessageSetPayloadMarker(notificationMessage));
270 SuccessOrExit(error = otMessageAppend(notificationMessage, mResourceContent,
271 static_cast<uint16_t>(strlen(mResourceContent))));
272
273 SuccessOrExit(error = otCoapSendRequest(GetInstancePtr(), notificationMessage, &messageInfo,
274 &Coap::HandleNotificationResponse, this));
275 }
276 #endif // OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
277 }
278 else
279 {
280 OutputLine("%s", mResourceContent);
281 }
282
283 exit:
284
285 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
286 if ((error != OT_ERROR_NONE) && (notificationMessage != nullptr))
287 {
288 otMessageFree(notificationMessage);
289 }
290 #endif
291
292 return error;
293 }
294
295 /**
296 * @cli coap start
297 * @code
298 * coap start
299 * Done
300 * @endcode
301 * @par
302 * Starts the CoAP server. @moreinfo{@coap}.
303 * @sa otCoapStart
304 */
Process(Arg aArgs[])305 template <> otError Coap::Process<Cmd("start")>(Arg aArgs[])
306 {
307 OT_UNUSED_VARIABLE(aArgs);
308
309 return otCoapStart(GetInstancePtr(), OT_DEFAULT_COAP_PORT);
310 }
311
312 /**
313 * @cli coap stop
314 * @code
315 * coap stop
316 * Done
317 * @endcode
318 * @par api_copy
319 * #otCoapStop
320 */
Process(Arg aArgs[])321 template <> otError Coap::Process<Cmd("stop")>(Arg aArgs[])
322 {
323 OT_UNUSED_VARIABLE(aArgs);
324
325 #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
326 otCoapRemoveBlockWiseResource(GetInstancePtr(), &mResource);
327 #else
328 otCoapRemoveResource(GetInstancePtr(), &mResource);
329 #endif
330
331 return otCoapStop(GetInstancePtr());
332 }
333
334 /**
335 * @cli coap parameters(get,set)
336 * @code
337 * coap parameters request
338 * Transmission parameters for request:
339 * ACK_TIMEOUT=1000 ms, ACK_RANDOM_FACTOR=255/254, MAX_RETRANSMIT=2
340 * Done
341 * @endcode
342 * @code
343 * coap parameters request default
344 * Transmission parameters for request:
345 * default
346 * Done
347 * @endcode
348 * @code
349 * coap parameters request 1000 255 254 2
350 * Transmission parameters for request:
351 * ACK_TIMEOUT=1000 ms, ACK_RANDOM_FACTOR=255/254, MAX_RETRANSMIT=2
352 * Done
353 * @endcode
354 * @cparam coap parameters @ca{type} [@ca{default} | <!--
355 * -->@ca{ack_timeout ack_random_factor_numerator <!--
356 * -->ack_random_factor_denominator max_retransmit}]
357 * * `type`: `request` for CoAP requests, or `response` for CoAP responses.
358 If no more parameters are given, the command prints the current configuration.
359 * * `default`: Sets the transmission parameters to
360 the following default values:
361 * * `ack_timeout`: 2000 milliseconds
362 * * `ack_random_factor_numerator`: 3
363 * * `ack_random_factor_denominator`: 2
364 * * `max_retransmit`: 4
365 * * `ack_timeout`: The `ACK_TIMEOUT` (0-UINT32_MAX) in milliseconds.
366 Refer to RFC7252.
367 * * `ack_random_factor_numerator`:
368 The `ACK_RANDOM_FACTOR` numerator, with possible values
369 of 0-255. Refer to RFC7252.
370 * * `ack_random_factor_denominator`:
371 * The `ACK_RANDOM_FACTOR` denominator, with possible values
372 * of 0-255. Refer to RFC7252.
373 * * `max_retransmit`: The `MAX_RETRANSMIT` (0-255). Refer to RFC7252.
374 * @par
375 * Gets current CoAP parameter values if the command is run with no optional
376 * parameters.
377 * @par
378 * Sets the CoAP parameters either to their default values or to the values
379 * you specify, depending on the syntax chosen.
380 */
Process(Arg aArgs[])381 template <> otError Coap::Process<Cmd("parameters")>(Arg aArgs[])
382 {
383 otError error = OT_ERROR_NONE;
384 bool *defaultTxParameters;
385 otCoapTxParameters *txParameters;
386
387 if (aArgs[0] == "request")
388 {
389 txParameters = &mRequestTxParameters;
390 defaultTxParameters = &mUseDefaultRequestTxParameters;
391 }
392 else if (aArgs[0] == "response")
393 {
394 txParameters = &mResponseTxParameters;
395 defaultTxParameters = &mUseDefaultResponseTxParameters;
396 }
397 else
398 {
399 ExitNow(error = OT_ERROR_INVALID_ARGS);
400 }
401
402 if (!aArgs[1].IsEmpty())
403 {
404 if (aArgs[1] == "default")
405 {
406 *defaultTxParameters = true;
407 }
408 else
409 {
410 SuccessOrExit(error = aArgs[1].ParseAsUint32(txParameters->mAckTimeout));
411 SuccessOrExit(error = aArgs[2].ParseAsUint8(txParameters->mAckRandomFactorNumerator));
412 SuccessOrExit(error = aArgs[3].ParseAsUint8(txParameters->mAckRandomFactorDenominator));
413 SuccessOrExit(error = aArgs[4].ParseAsUint8(txParameters->mMaxRetransmit));
414
415 VerifyOrExit(txParameters->mAckRandomFactorNumerator > txParameters->mAckRandomFactorDenominator,
416 error = OT_ERROR_INVALID_ARGS);
417
418 *defaultTxParameters = false;
419 }
420 }
421
422 OutputLine("Transmission parameters for %s:", aArgs[0].GetCString());
423
424 if (*defaultTxParameters)
425 {
426 OutputLine("default");
427 }
428 else
429 {
430 OutputLine("ACK_TIMEOUT=%lu ms, ACK_RANDOM_FACTOR=%u/%u, MAX_RETRANSMIT=%u", ToUlong(txParameters->mAckTimeout),
431 txParameters->mAckRandomFactorNumerator, txParameters->mAckRandomFactorDenominator,
432 txParameters->mMaxRetransmit);
433 }
434
435 exit:
436 return error;
437 }
438
439 /**
440 * @cli coap get
441 * @code
442 * coap get fdde:ad00:beef:0:2780:9423:166c:1aac test-resource
443 * Done
444 * @endcode
445 * @code
446 * coap get fdde:ad00:beef:0:2780:9423:166c:1aac test-resource block-1024
447 * Done
448 * @endcode
449 * @cparam coap get @ca{address} @ca{uri-path} [@ca{type}]
450 * * `address`: IPv6 address of the CoAP server.
451 * * `uri-path`: URI path of the resource.
452 * * `type`:
453 * * `con`: Confirmable
454 * * `non-con`: Non-confirmable (default)
455 * * `block-`: Use this option, followed by the block-wise value,
456 * if the response should be transferred block-wise. Valid
457 * values are: `block-16`, `block-32`, `block-64`, `block-128`,
458 * `block-256`, `block-512`, or `block-1024`.
459 * @par
460 * Gets information about the specified CoAP resource on the CoAP server.
461 */
Process(Arg aArgs[])462 template <> otError Coap::Process<Cmd("get")>(Arg aArgs[]) { return ProcessRequest(aArgs, OT_COAP_CODE_GET); }
463
464 /**
465 * @cli coap post
466 * @code
467 * coap post fdde:ad00:beef:0:2780:9423:166c:1aac test-resource con hellothere
468 * Done
469 * @endcode
470 * @code
471 * coap post fdde:ad00:beef:0:2780:9423:166c:1aac test-resource block-1024 10
472 * Done
473 * @endcode
474 * @cparam coap post @ca{address} @ca{uri-path} [@ca{type}] [@ca{payload}]
475 * * `address`: IPv6 address of the CoAP server.
476 * * `uri-path`: URI path of the resource.
477 * * `type`:
478 * * `con`: Confirmable
479 * * `non-con`: Non-confirmable (default)
480 * * `block-`: Use this option, followed by the block-wise value,
481 * to send blocks with a randomly generated number of bytes
482 * for the payload. Valid values are:
483 * `block-16`, `block-32`, `block-64`, `block-128`,
484 * `block-256`, `block-512`, or `block-1024`.
485 * * `payload`: CoAP payload request, which if used is either a string or an
486 * integer, depending on the `type`. If the `type` is `con` or `non-con`,
487 * the `payload` parameter is optional. If you leave out the
488 * `payload` parameter, an empty payload is sent. However, If you use the
489 * `payload` parameter, its value must be a string, such as
490 * `hellothere`. If the `type` is `block-`,
491 * the value of the`payload` parameter must be an integer that specifies
492 * the number of blocks to send. The `block-` type requires
493 * `OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE` to be set.
494 * @par
495 * Creates the specified CoAP resource. @moreinfo{@coap}.
496 */
Process(Arg aArgs[])497 template <> otError Coap::Process<Cmd("post")>(Arg aArgs[]) { return ProcessRequest(aArgs, OT_COAP_CODE_POST); }
498
499 /**
500 * @cli coap put
501 * @code
502 * coap put fdde:ad00:beef:0:2780:9423:166c:1aac test-resource con hellothere
503 * Done
504 * @endcode
505 * @code
506 * coap put fdde:ad00:beef:0:2780:9423:166c:1aac test-resource block-1024 10
507 * Done
508 * @endcode
509 * @cparam coap put @ca{address} @ca{uri-path} [@ca{type}] [@ca{payload}]
510 * * `address`: IPv6 address of the CoAP server.
511 * * `uri-path`: URI path of the resource.
512 * * `type`:
513 * * `con`: Confirmable
514 * * `non-con`: Non-confirmable (default)
515 * * `block-`: Use this option, followed by the block-wise value,
516 * to send blocks with a randomly generated number of bytes
517 * for the payload. Valid values are:
518 * `block-16`, `block-32`, `block-64`, `block-128`,
519 * `block-256`, `block-512`, or `block-1024`.
520 * * `payload`: CoAP payload request, which if used is either a string or an
521 * integer, depending on the `type`. If the `type` is `con` or `non-con`,
522 * the `payload` parameter is optional. If you leave out the
523 * `payload` parameter, an empty payload is sent. However, If you use the
524 * `payload` parameter, its value must be a string, such as
525 * `hellothere`. If the `type` is `block-`,
526 * the value of the`payload` parameter must be an integer that specifies
527 * the number of blocks to send. The `block-` type requires
528 * `OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE` to be set.
529 * @par
530 * Modifies the specified CoAP resource. @moreinfo{@coap}.
531 */
Process(Arg aArgs[])532 template <> otError Coap::Process<Cmd("put")>(Arg aArgs[]) { return ProcessRequest(aArgs, OT_COAP_CODE_PUT); }
533
534 /**
535 * @cli coap delete
536 * @code
537 * coap delete fdde:ad00:beef:0:2780:9423:166c:1aac test-resource con hellothere
538 * Done
539 * @endcode
540 * @cparam coap delete @ca{address} @ca{uri-path} [@ca{type}] [@ca{payload}]
541 * * `address`: IPv6 address of the CoAP server.
542 * * `uri-path`: URI path of the resource.
543 * * `type`:
544 * * `con`: Confirmable
545 * * `non-con`: Non-confirmable (default)
546 * * `payload`: The CoAP payload string. For example, `hellothere`.
547 * @par
548 * Deletes the specified CoAP resource.
549 */
Process(Arg aArgs[])550 template <> otError Coap::Process<Cmd("delete")>(Arg aArgs[]) { return ProcessRequest(aArgs, OT_COAP_CODE_DELETE); }
551
552 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
553 /**
554 * @cli coap observe
555 * @code
556 * coap observe fdde:ad00:beef:0:2780:9423:166c:1aac test-resource
557 * Done
558 * @endcode
559 * @cparam coap observe @ca{address} @ca{uri-path} [@ca{type}]
560 * * `address`: IPv6 address of the CoAP server.
561 * * `uri-path`: URI path of the resource.
562 * * `type`:
563 * * `con`: Confirmable
564 * * `non-con`: Non-confirmable (default).
565 * @par
566 * Triggers a subscription request which allows the CoAP client to
567 * observe the specified resource on the CoAP server for possible changes
568 * in its state.
569 * @note This command is available only when `OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE` is set.
570 */
Process(Arg aArgs[])571 template <> otError Coap::Process<Cmd("observe")>(Arg aArgs[])
572 {
573 return ProcessRequest(aArgs, OT_COAP_CODE_GET, /* aCoapObserve */ true);
574 }
575 #endif
576
577 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
ProcessRequest(Arg aArgs[],otCoapCode aCoapCode,bool aCoapObserve)578 otError Coap::ProcessRequest(Arg aArgs[], otCoapCode aCoapCode, bool aCoapObserve)
579 #else
580 otError Coap::ProcessRequest(Arg aArgs[], otCoapCode aCoapCode)
581 #endif
582 {
583 otError error = OT_ERROR_NONE;
584 otMessage *message = nullptr;
585 otMessageInfo messageInfo;
586 uint16_t payloadLength = 0;
587
588 // Default parameters
589 char coapUri[kMaxUriLength] = "test";
590 otCoapType coapType = OT_COAP_TYPE_NON_CONFIRMABLE;
591 otIp6Address coapDestinationIp;
592 #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
593 bool coapBlock = false;
594 otCoapBlockSzx coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_16;
595 BlockType coapBlockType = (aCoapCode == OT_COAP_CODE_GET) ? kBlockType2 : kBlockType1;
596 #endif
597
598 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE && OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
599 if (aCoapObserve)
600 {
601 coapBlockType = kBlockType1;
602 }
603 #endif
604
605 SuccessOrExit(error = aArgs[0].ParseAsIp6Address(coapDestinationIp));
606
607 VerifyOrExit(!aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
608 VerifyOrExit(aArgs[1].GetLength() < sizeof(coapUri), error = OT_ERROR_INVALID_ARGS);
609 strcpy(coapUri, aArgs[1].GetCString());
610
611 // CoAP-Type
612 if (!aArgs[2].IsEmpty())
613 {
614 if (aArgs[2] == "con")
615 {
616 coapType = OT_COAP_TYPE_CONFIRMABLE;
617 }
618 #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
619 else if (aArgs[2] == "block-16")
620 {
621 coapType = OT_COAP_TYPE_CONFIRMABLE;
622 coapBlock = true;
623 coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_16;
624 }
625 else if (aArgs[2] == "block-32")
626 {
627 coapType = OT_COAP_TYPE_CONFIRMABLE;
628 coapBlock = true;
629 coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_32;
630 }
631 else if (aArgs[2] == "block-64")
632 {
633 coapType = OT_COAP_TYPE_CONFIRMABLE;
634 coapBlock = true;
635 coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_64;
636 }
637 else if (aArgs[2] == "block-128")
638 {
639 coapType = OT_COAP_TYPE_CONFIRMABLE;
640 coapBlock = true;
641 coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_128;
642 }
643 else if (aArgs[2] == "block-256")
644 {
645 coapType = OT_COAP_TYPE_CONFIRMABLE;
646 coapBlock = true;
647 coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_256;
648 }
649 else if (aArgs[2] == "block-512")
650 {
651 coapType = OT_COAP_TYPE_CONFIRMABLE;
652 coapBlock = true;
653 coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_512;
654 }
655 else if (aArgs[2] == "block-1024")
656 {
657 coapType = OT_COAP_TYPE_CONFIRMABLE;
658 coapBlock = true;
659 coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_1024;
660 }
661 #endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
662 }
663
664 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
665 if (aCoapObserve && mRequestTokenLength)
666 {
667 // New observe request, cancel any existing observation
668 SuccessOrExit(error = CancelResourceSubscription());
669 }
670 #endif
671
672 message = otCoapNewMessage(GetInstancePtr(), nullptr);
673 VerifyOrExit(message != nullptr, error = OT_ERROR_NO_BUFS);
674
675 otCoapMessageInit(message, coapType, aCoapCode);
676 otCoapMessageGenerateToken(message, OT_COAP_DEFAULT_TOKEN_LENGTH);
677
678 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
679 if (aCoapObserve)
680 {
681 SuccessOrExit(error = otCoapMessageAppendObserveOption(message, 0));
682 }
683 #endif
684
685 SuccessOrExit(error = otCoapMessageAppendUriPathOptions(message, coapUri));
686
687 #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
688 if (coapBlock)
689 {
690 if (coapBlockType == kBlockType1)
691 {
692 SuccessOrExit(error = otCoapMessageAppendBlock1Option(message, 0, true, coapBlockSize));
693 }
694 else
695 {
696 SuccessOrExit(error = otCoapMessageAppendBlock2Option(message, 0, false, coapBlockSize));
697 }
698 }
699 #endif
700
701 if (!aArgs[3].IsEmpty())
702 {
703 #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
704 if (coapBlock)
705 {
706 SuccessOrExit(error = aArgs[3].ParseAsUint32(mBlockCount));
707 }
708 else
709 {
710 #endif
711 payloadLength = aArgs[3].GetLength();
712
713 if (payloadLength > 0)
714 {
715 SuccessOrExit(error = otCoapMessageSetPayloadMarker(message));
716 }
717 #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
718 }
719 #endif
720 }
721
722 // Embed content into message if given
723 if (payloadLength > 0)
724 {
725 SuccessOrExit(error = otMessageAppend(message, aArgs[3].GetCString(), payloadLength));
726 }
727
728 ClearAllBytes(messageInfo);
729 messageInfo.mPeerAddr = coapDestinationIp;
730 messageInfo.mPeerPort = OT_DEFAULT_COAP_PORT;
731
732 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
733 if (aCoapObserve)
734 {
735 // Make a note of the message details for later so we can cancel it later.
736 memcpy(&mRequestAddr, &coapDestinationIp, sizeof(mRequestAddr));
737 mRequestTokenLength = otCoapMessageGetTokenLength(message);
738 memcpy(mRequestToken, otCoapMessageGetToken(message), mRequestTokenLength);
739 // Use `memcpy` instead of `strncpy` here because GCC will give warnings for `strncpy` when the dest's length is
740 // not bigger than the src's length.
741 memcpy(mRequestUri, coapUri, sizeof(mRequestUri) - 1);
742 }
743 #endif
744
745 if ((coapType == OT_COAP_TYPE_CONFIRMABLE) || (aCoapCode == OT_COAP_CODE_GET))
746 {
747 #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
748 if (coapBlock)
749 {
750 if (aCoapCode == OT_COAP_CODE_PUT || aCoapCode == OT_COAP_CODE_POST)
751 {
752 SuccessOrExit(error = otCoapMessageSetPayloadMarker(message));
753 }
754 error = otCoapSendRequestBlockWiseWithParameters(GetInstancePtr(), message, &messageInfo,
755 &Coap::HandleResponse, this, GetRequestTxParameters(),
756 Coap::BlockwiseTransmitHook, Coap::BlockwiseReceiveHook);
757 }
758 else
759 {
760 #endif
761 error = otCoapSendRequestWithParameters(GetInstancePtr(), message, &messageInfo, &Coap::HandleResponse,
762 this, GetRequestTxParameters());
763 #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
764 }
765 #endif
766 }
767 else
768 {
769 error = otCoapSendRequestWithParameters(GetInstancePtr(), message, &messageInfo, nullptr, nullptr,
770 GetResponseTxParameters());
771 }
772
773 exit:
774
775 if ((error != OT_ERROR_NONE) && (message != nullptr))
776 {
777 otMessageFree(message);
778 }
779
780 return error;
781 }
782
Process(Arg aArgs[])783 otError Coap::Process(Arg aArgs[])
784 {
785 #define CmdEntry(aCommandString) \
786 { \
787 aCommandString, &Coap::Process<Cmd(aCommandString)> \
788 }
789
790 static constexpr Command kCommands[] = {
791 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
792 CmdEntry("cancel"),
793 #endif
794 CmdEntry("delete"),
795 CmdEntry("get"),
796 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
797 CmdEntry("observe"),
798 #endif
799 CmdEntry("parameters"),
800 CmdEntry("post"),
801 CmdEntry("put"),
802 CmdEntry("resource"),
803 CmdEntry("set"),
804 CmdEntry("start"),
805 CmdEntry("stop"),
806 };
807
808 #undef CmdEntry
809
810 static_assert(BinarySearch::IsSorted(kCommands), "kCommands is not sorted");
811
812 otError error = OT_ERROR_INVALID_COMMAND;
813 const Command *command;
814
815 if (aArgs[0].IsEmpty() || (aArgs[0] == "help"))
816 {
817 OutputCommandTable(kCommands);
818 ExitNow(error = aArgs[0].IsEmpty() ? OT_ERROR_INVALID_ARGS : OT_ERROR_NONE);
819 }
820
821 command = BinarySearch::Find(aArgs[0].GetCString(), kCommands);
822 VerifyOrExit(command != nullptr);
823
824 error = (this->*command->mHandler)(aArgs + 1);
825
826 exit:
827 return error;
828 }
829
HandleRequest(void * aContext,otMessage * aMessage,const otMessageInfo * aMessageInfo)830 void Coap::HandleRequest(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo)
831 {
832 static_cast<Coap *>(aContext)->HandleRequest(aMessage, aMessageInfo);
833 }
834
HandleRequest(otMessage * aMessage,const otMessageInfo * aMessageInfo)835 void Coap::HandleRequest(otMessage *aMessage, const otMessageInfo *aMessageInfo)
836 {
837 otError error = OT_ERROR_NONE;
838 otMessage *responseMessage = nullptr;
839 otCoapCode responseCode = OT_COAP_CODE_EMPTY;
840 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
841 uint64_t observe = 0;
842 bool observePresent = false;
843 #endif
844 #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
845 uint64_t blockValue = 0;
846 bool blockPresent = false;
847 #endif
848 #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE || OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
849 otCoapOptionIterator iterator;
850 #endif
851
852 OutputFormat("coap request from ");
853 OutputIp6Address(aMessageInfo->mPeerAddr);
854 OutputFormat(" ");
855
856 switch (otCoapMessageGetCode(aMessage))
857 {
858 case OT_COAP_CODE_GET:
859 OutputFormat("GET");
860 #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE || OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
861 SuccessOrExit(error = otCoapOptionIteratorInit(&iterator, aMessage));
862 #endif
863 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
864 if (otCoapOptionIteratorGetFirstOptionMatching(&iterator, OT_COAP_OPTION_OBSERVE) != nullptr)
865 {
866 SuccessOrExit(error = otCoapOptionIteratorGetOptionUintValue(&iterator, &observe));
867 observePresent = true;
868
869 OutputFormat(" OBS=");
870 OutputUint64(observe);
871 }
872 #endif
873 #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
874 if (otCoapOptionIteratorGetFirstOptionMatching(&iterator, OT_COAP_OPTION_BLOCK2) != nullptr)
875 {
876 SuccessOrExit(error = otCoapOptionIteratorGetOptionUintValue(&iterator, &blockValue));
877 blockPresent = true;
878 }
879 #endif
880 break;
881
882 case OT_COAP_CODE_DELETE:
883 OutputFormat("DELETE");
884 break;
885
886 case OT_COAP_CODE_PUT:
887 OutputFormat("PUT");
888 break;
889
890 case OT_COAP_CODE_POST:
891 OutputFormat("POST");
892 break;
893
894 default:
895 OutputLine("Undefined");
896 ExitNow(error = OT_ERROR_PARSE);
897 }
898
899 PrintPayload(aMessage);
900
901 if (otCoapMessageGetType(aMessage) == OT_COAP_TYPE_CONFIRMABLE ||
902 otCoapMessageGetCode(aMessage) == OT_COAP_CODE_GET)
903 {
904 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
905 if (observePresent && (mSubscriberTokenLength > 0) && (observe == 0))
906 {
907 // There is already a subscriber
908 responseCode = OT_COAP_CODE_SERVICE_UNAVAILABLE;
909 }
910 else
911 #endif
912 if (otCoapMessageGetCode(aMessage) == OT_COAP_CODE_GET)
913 {
914 responseCode = OT_COAP_CODE_CONTENT;
915 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
916 if (observePresent)
917 {
918 if (observe == 0)
919 {
920 // New subscriber
921 OutputLine("Subscribing client");
922 mSubscriberSock.mAddress = aMessageInfo->mPeerAddr;
923 mSubscriberSock.mPort = aMessageInfo->mPeerPort;
924 mSubscriberTokenLength = otCoapMessageGetTokenLength(aMessage);
925 memcpy(mSubscriberToken, otCoapMessageGetToken(aMessage), mSubscriberTokenLength);
926
927 /*
928 * Implementer note.
929 *
930 * Here, we try to match a confirmable GET request with confirmable
931 * notifications, however this is not a requirement of RFC7641:
932 * the server can send notifications of either type regardless of
933 * what the client used to subscribe initially.
934 */
935 mSubscriberConfirmableNotifications = (otCoapMessageGetType(aMessage) == OT_COAP_TYPE_CONFIRMABLE);
936 }
937 else if (observe == 1)
938 {
939 // See if it matches our subscriber token
940 if ((otCoapMessageGetTokenLength(aMessage) == mSubscriberTokenLength) &&
941 (memcmp(otCoapMessageGetToken(aMessage), mSubscriberToken, mSubscriberTokenLength) == 0))
942 {
943 // Unsubscribe request
944 CancelSubscriber();
945 }
946 }
947 }
948 #endif // OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
949 }
950 else
951 {
952 responseCode = OT_COAP_CODE_CHANGED;
953 }
954
955 responseMessage = otCoapNewMessage(GetInstancePtr(), nullptr);
956 VerifyOrExit(responseMessage != nullptr, error = OT_ERROR_NO_BUFS);
957
958 SuccessOrExit(
959 error = otCoapMessageInitResponse(responseMessage, aMessage, OT_COAP_TYPE_ACKNOWLEDGMENT, responseCode));
960
961 if (responseCode == OT_COAP_CODE_CONTENT)
962 {
963 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
964 if (observePresent && (observe == 0))
965 {
966 SuccessOrExit(error = otCoapMessageAppendObserveOption(responseMessage, mObserveSerial++));
967 }
968 #endif
969 #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
970 if (blockPresent)
971 {
972 SuccessOrExit(error = otCoapMessageAppendBlock2Option(responseMessage,
973 static_cast<uint32_t>(blockValue >> 4), true,
974 static_cast<otCoapBlockSzx>(blockValue & 0x7)));
975 SuccessOrExit(error = otCoapMessageSetPayloadMarker(responseMessage));
976 }
977 else
978 {
979 #endif
980 SuccessOrExit(error = otCoapMessageSetPayloadMarker(responseMessage));
981 SuccessOrExit(error = otMessageAppend(responseMessage, mResourceContent,
982 static_cast<uint16_t>(strlen(mResourceContent))));
983 #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
984 }
985 #endif
986 }
987
988 #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
989 if (blockPresent)
990 {
991 SuccessOrExit(error = otCoapSendResponseBlockWiseWithParameters(GetInstancePtr(), responseMessage,
992 aMessageInfo, GetResponseTxParameters(),
993 this, mResource.mTransmitHook));
994 }
995 else
996 {
997 #endif
998 SuccessOrExit(error = otCoapSendResponseWithParameters(GetInstancePtr(), responseMessage, aMessageInfo,
999 GetResponseTxParameters()));
1000 #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
1001 }
1002 #endif
1003 }
1004
1005 exit:
1006
1007 if (error != OT_ERROR_NONE)
1008 {
1009 if (responseMessage != nullptr)
1010 {
1011 OutputLine("coap send response error %d: %s", error, otThreadErrorToString(error));
1012 otMessageFree(responseMessage);
1013 }
1014 }
1015 else if (responseCode >= OT_COAP_CODE_RESPONSE_MIN)
1016 {
1017 OutputLine("coap response sent");
1018 }
1019 }
1020
1021 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
HandleNotificationResponse(void * aContext,otMessage * aMessage,const otMessageInfo * aMessageInfo,otError aError)1022 void Coap::HandleNotificationResponse(void *aContext,
1023 otMessage *aMessage,
1024 const otMessageInfo *aMessageInfo,
1025 otError aError)
1026 {
1027 static_cast<Coap *>(aContext)->HandleNotificationResponse(aMessage, aMessageInfo, aError);
1028 }
1029
HandleNotificationResponse(otMessage * aMessage,const otMessageInfo * aMessageInfo,otError aError)1030 void Coap::HandleNotificationResponse(otMessage *aMessage, const otMessageInfo *aMessageInfo, otError aError)
1031 {
1032 OT_UNUSED_VARIABLE(aMessage);
1033
1034 switch (aError)
1035 {
1036 case OT_ERROR_NONE:
1037 if (aMessageInfo != nullptr)
1038 {
1039 OutputFormat("Received ACK in reply to notification from ");
1040 OutputIp6AddressLine(aMessageInfo->mPeerAddr);
1041 }
1042 break;
1043
1044 default:
1045 OutputLine("coap receive notification response error %d: %s", aError, otThreadErrorToString(aError));
1046 CancelSubscriber();
1047 break;
1048 }
1049 }
1050 #endif // OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
1051
HandleResponse(void * aContext,otMessage * aMessage,const otMessageInfo * aMessageInfo,otError aError)1052 void Coap::HandleResponse(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo, otError aError)
1053 {
1054 static_cast<Coap *>(aContext)->HandleResponse(aMessage, aMessageInfo, aError);
1055 }
1056
HandleResponse(otMessage * aMessage,const otMessageInfo * aMessageInfo,otError aError)1057 void Coap::HandleResponse(otMessage *aMessage, const otMessageInfo *aMessageInfo, otError aError)
1058 {
1059 if (aError != OT_ERROR_NONE)
1060 {
1061 OutputLine("coap receive response error %d: %s", aError, otThreadErrorToString(aError));
1062 }
1063 else if ((aMessageInfo != nullptr) && (aMessage != nullptr))
1064 {
1065 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
1066 otCoapOptionIterator iterator;
1067 #endif
1068
1069 OutputFormat("coap response from ");
1070 OutputIp6Address(aMessageInfo->mPeerAddr);
1071
1072 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
1073 if (otCoapOptionIteratorInit(&iterator, aMessage) == OT_ERROR_NONE)
1074 {
1075 const otCoapOption *observeOpt =
1076 otCoapOptionIteratorGetFirstOptionMatching(&iterator, OT_COAP_OPTION_OBSERVE);
1077
1078 if (observeOpt != nullptr)
1079 {
1080 uint64_t observeVal = 0;
1081 otError error = otCoapOptionIteratorGetOptionUintValue(&iterator, &observeVal);
1082
1083 if (error == OT_ERROR_NONE)
1084 {
1085 OutputFormat(" OBS=");
1086 OutputUint64(observeVal);
1087 }
1088 }
1089 }
1090 #endif
1091 PrintPayload(aMessage);
1092 }
1093 }
1094
1095 #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
BlockwiseReceiveHook(void * aContext,const uint8_t * aBlock,uint32_t aPosition,uint16_t aBlockLength,bool aMore,uint32_t aTotalLength)1096 otError Coap::BlockwiseReceiveHook(void *aContext,
1097 const uint8_t *aBlock,
1098 uint32_t aPosition,
1099 uint16_t aBlockLength,
1100 bool aMore,
1101 uint32_t aTotalLength)
1102 {
1103 return static_cast<Coap *>(aContext)->BlockwiseReceiveHook(aBlock, aPosition, aBlockLength, aMore, aTotalLength);
1104 }
1105
BlockwiseReceiveHook(const uint8_t * aBlock,uint32_t aPosition,uint16_t aBlockLength,bool aMore,uint32_t aTotalLength)1106 otError Coap::BlockwiseReceiveHook(const uint8_t *aBlock,
1107 uint32_t aPosition,
1108 uint16_t aBlockLength,
1109 bool aMore,
1110 uint32_t aTotalLength)
1111 {
1112 OT_UNUSED_VARIABLE(aMore);
1113 OT_UNUSED_VARIABLE(aTotalLength);
1114
1115 OutputLine("received block: Num %i Len %i", aPosition / aBlockLength, aBlockLength);
1116
1117 for (uint16_t i = 0; i < aBlockLength / 16; i++)
1118 {
1119 OutputBytesLine(&aBlock[i * 16], 16);
1120 }
1121
1122 return OT_ERROR_NONE;
1123 }
1124
BlockwiseTransmitHook(void * aContext,uint8_t * aBlock,uint32_t aPosition,uint16_t * aBlockLength,bool * aMore)1125 otError Coap::BlockwiseTransmitHook(void *aContext,
1126 uint8_t *aBlock,
1127 uint32_t aPosition,
1128 uint16_t *aBlockLength,
1129 bool *aMore)
1130 {
1131 return static_cast<Coap *>(aContext)->BlockwiseTransmitHook(aBlock, aPosition, aBlockLength, aMore);
1132 }
1133
BlockwiseTransmitHook(uint8_t * aBlock,uint32_t aPosition,uint16_t * aBlockLength,bool * aMore)1134 otError Coap::BlockwiseTransmitHook(uint8_t *aBlock, uint32_t aPosition, uint16_t *aBlockLength, bool *aMore)
1135 {
1136 static uint32_t blockCount = 0;
1137 OT_UNUSED_VARIABLE(aPosition);
1138
1139 // Send a random payload
1140 otRandomNonCryptoFillBuffer(aBlock, *aBlockLength);
1141
1142 OutputLine("send block: Num %i Len %i", blockCount, *aBlockLength);
1143
1144 for (uint16_t i = 0; i < *aBlockLength / 16; i++)
1145 {
1146 OutputBytesLine(&aBlock[i * 16], 16);
1147 }
1148
1149 if (blockCount == mBlockCount - 1)
1150 {
1151 blockCount = 0;
1152 *aMore = false;
1153 }
1154 else
1155 {
1156 *aMore = true;
1157 blockCount++;
1158 }
1159
1160 return OT_ERROR_NONE;
1161 }
1162 #endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
1163
1164 } // namespace Cli
1165 } // namespace ot
1166
1167 #endif // OPENTHREAD_CONFIG_COAP_API_ENABLE
1168