1 /*
2 * Copyright (c) 2016-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" AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28 /**
29 * @file
30 * This file implements raw link required Spinel interface to the OpenThread stack.
31 */
32
33 #include "ncp_base.hpp"
34
35 #include <openthread/link.h>
36 #include <openthread/link_raw.h>
37 #include <openthread/ncp.h>
38 #include <openthread/platform/radio.h>
39 #include <openthread/platform/time.h>
40
41 #include "common/code_utils.hpp"
42 #include "common/debug.hpp"
43 #include "common/instance.hpp"
44 #include "mac/mac_frame.hpp"
45
46 #if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
47
48 namespace ot {
49 namespace Ncp {
50
51 #if OPENTHREAD_RADIO
HandlePropertyGet(void)52 template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_RCP_API_VERSION>(void)
53 {
54 return mEncoder.WriteUintPacked(SPINEL_RCP_API_VERSION);
55 }
56 #endif
57
58 // ----------------------------------------------------------------------------
59 // MARK: Raw Link-Layer Datapath Glue
60 // ----------------------------------------------------------------------------
61
PackRadioFrame(otRadioFrame * aFrame,otError aError)62 otError NcpBase::PackRadioFrame(otRadioFrame *aFrame, otError aError)
63 {
64 otError error = OT_ERROR_FAILED;
65 uint16_t flags = 0;
66
67 if (aFrame != nullptr && aError == OT_ERROR_NONE)
68 {
69 // Append the frame contents
70 SuccessOrExit(mEncoder.WriteDataWithLen(aFrame->mPsdu, aFrame->mLength));
71 }
72 else
73 {
74 // Append length
75 SuccessOrExit(mEncoder.WriteUint16(0));
76 }
77
78 // Append metadata (rssi, etc)
79 SuccessOrExit(mEncoder.WriteInt8(aFrame ? aFrame->mInfo.mRxInfo.mRssi : 0)); // RSSI
80 SuccessOrExit(mEncoder.WriteInt8(-128)); // Noise Floor (Currently unused)
81
82 if (aFrame != nullptr)
83 {
84 if (aFrame->mInfo.mRxInfo.mAckedWithFramePending)
85 {
86 flags |= SPINEL_MD_FLAG_ACKED_FP;
87 }
88
89 if (aFrame->mInfo.mRxInfo.mAckedWithSecEnhAck)
90 {
91 flags |= SPINEL_MD_FLAG_ACKED_SEC;
92 }
93 }
94
95 SuccessOrExit(mEncoder.WriteUint16(flags)); // Flags
96
97 SuccessOrExit(mEncoder.OpenStruct()); // PHY-data
98 SuccessOrExit(mEncoder.WriteUint8(aFrame ? aFrame->mChannel : 0)); // 802.15.4 channel (Receive channel)
99 SuccessOrExit(mEncoder.WriteUint8(aFrame ? aFrame->mInfo.mRxInfo.mLqi
100 : static_cast<uint8_t>(OT_RADIO_LQI_NONE))); // 802.15.4 LQI
101
102 SuccessOrExit(mEncoder.WriteUint64(aFrame ? aFrame->mInfo.mRxInfo.mTimestamp : 0)); // The timestamp in microseconds
103 SuccessOrExit(mEncoder.CloseStruct());
104
105 SuccessOrExit(mEncoder.OpenStruct()); // Vendor-data
106 SuccessOrExit(mEncoder.WriteUintPacked(aError)); // Receive error
107 SuccessOrExit(mEncoder.CloseStruct());
108
109 SuccessOrExit(mEncoder.OpenStruct()); // MAC-data
110 SuccessOrExit(mEncoder.WriteUint8(aFrame ? aFrame->mInfo.mRxInfo.mAckKeyId : 0)); // The ACK auxiliary key ID
111 SuccessOrExit(
112 mEncoder.WriteUint32(aFrame ? aFrame->mInfo.mRxInfo.mAckFrameCounter : 0)); // The ACK auxiliary frame counter
113 SuccessOrExit(mEncoder.CloseStruct());
114
115 error = OT_ERROR_NONE;
116
117 exit:
118 return error;
119 }
120
LinkRawReceiveDone(otInstance *,otRadioFrame * aFrame,otError aError)121 void NcpBase::LinkRawReceiveDone(otInstance *, otRadioFrame *aFrame, otError aError)
122 {
123 sNcpInstance->LinkRawReceiveDone(aFrame, aError);
124 }
125
LinkRawReceiveDone(otRadioFrame * aFrame,otError aError)126 void NcpBase::LinkRawReceiveDone(otRadioFrame *aFrame, otError aError)
127 {
128 uint8_t header = SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0;
129
130 // Append frame header
131 SuccessOrExit(mEncoder.BeginFrame(header, SPINEL_CMD_PROP_VALUE_IS, SPINEL_PROP_STREAM_RAW));
132
133 SuccessOrExit(PackRadioFrame(aFrame, aError));
134 SuccessOrExit(mEncoder.EndFrame());
135
136 exit:
137 return;
138 }
139
LinkRawTransmitDone(otInstance *,otRadioFrame * aFrame,otRadioFrame * aAckFrame,otError aError)140 void NcpBase::LinkRawTransmitDone(otInstance *, otRadioFrame *aFrame, otRadioFrame *aAckFrame, otError aError)
141 {
142 sNcpInstance->LinkRawTransmitDone(aFrame, aAckFrame, aError);
143 }
144
LinkRawTransmitDone(otRadioFrame * aFrame,otRadioFrame * aAckFrame,otError aError)145 void NcpBase::LinkRawTransmitDone(otRadioFrame *aFrame, otRadioFrame *aAckFrame, otError aError)
146 {
147 OT_UNUSED_VARIABLE(aFrame);
148
149 if (mCurTransmitTID)
150 {
151 uint8_t header = SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0 | mCurTransmitTID;
152 bool framePending = (aAckFrame != nullptr && static_cast<Mac::RxFrame *>(aAckFrame)->GetFramePending());
153 bool headerUpdated = static_cast<Mac::TxFrame *>(aFrame)->IsHeaderUpdated();
154
155 // Clear cached transmit TID
156 mCurTransmitTID = 0;
157
158 SuccessOrExit(mEncoder.BeginFrame(header, SPINEL_CMD_PROP_VALUE_IS, SPINEL_PROP_LAST_STATUS));
159 SuccessOrExit(mEncoder.WriteUintPacked(ThreadErrorToSpinelStatus(aError)));
160 SuccessOrExit(mEncoder.WriteBool(framePending));
161 SuccessOrExit(mEncoder.WriteBool(headerUpdated));
162
163 if (aError == OT_ERROR_NONE)
164 {
165 SuccessOrExit(PackRadioFrame(aAckFrame, aError));
166 }
167
168 if (static_cast<Mac::TxFrame *>(aFrame)->GetSecurityEnabled() && headerUpdated)
169 {
170 uint8_t keyId;
171 uint32_t frameCounter;
172
173 // Transmit frame auxiliary key index and frame counter
174 SuccessOrExit(static_cast<Mac::TxFrame *>(aFrame)->GetKeyId(keyId));
175 SuccessOrExit(static_cast<Mac::TxFrame *>(aFrame)->GetFrameCounter(frameCounter));
176
177 SuccessOrExit(mEncoder.WriteUint8(keyId));
178 SuccessOrExit(mEncoder.WriteUint32(frameCounter));
179 }
180
181 SuccessOrExit(mEncoder.EndFrame());
182 }
183
184 exit:
185 return;
186 }
187
LinkRawEnergyScanDone(otInstance *,int8_t aEnergyScanMaxRssi)188 void NcpBase::LinkRawEnergyScanDone(otInstance *, int8_t aEnergyScanMaxRssi)
189 {
190 sNcpInstance->LinkRawEnergyScanDone(aEnergyScanMaxRssi);
191 }
192
LinkRawEnergyScanDone(int8_t aEnergyScanMaxRssi)193 void NcpBase::LinkRawEnergyScanDone(int8_t aEnergyScanMaxRssi)
194 {
195 int8_t scanChannel = mCurScanChannel;
196
197 // Clear current scan channel
198 mCurScanChannel = kInvalidScanChannel;
199
200 // Make sure we are back listening on the original receive channel,
201 // since the energy scan could have been on a different channel.
202 IgnoreError(otLinkRawReceive(mInstance));
203
204 SuccessOrExit(mEncoder.BeginFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, SPINEL_CMD_PROP_VALUE_IS,
205 SPINEL_PROP_MAC_ENERGY_SCAN_RESULT));
206
207 SuccessOrExit(mEncoder.WriteUint8(static_cast<uint8_t>(scanChannel)));
208 SuccessOrExit(mEncoder.WriteInt8(aEnergyScanMaxRssi));
209 SuccessOrExit(mEncoder.EndFrame());
210
211 // We are finished with the scan, so send out
212 // a property update indicating such.
213 SuccessOrExit(mEncoder.BeginFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, SPINEL_CMD_PROP_VALUE_IS,
214 SPINEL_PROP_MAC_SCAN_STATE));
215
216 SuccessOrExit(mEncoder.WriteUint8(SPINEL_SCAN_STATE_IDLE));
217 SuccessOrExit(mEncoder.EndFrame());
218
219 exit:
220 return;
221 }
222
HandlePropertyGet(void)223 template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_RADIO_CAPS>(void)
224 {
225 return mEncoder.WriteUintPacked(otLinkRawGetCaps(mInstance));
226 }
227
HandlePropertyGet(void)228 template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MAC_SRC_MATCH_ENABLED>(void)
229 {
230 // TODO: Would be good to add an `otLinkRaw` API to give the status of source match.
231 return mEncoder.WriteBool(mSrcMatchEnabled);
232 }
233
HandlePropertyGet(void)234 template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_RCP_TIMESTAMP>(void)
235 {
236 otError error = OT_ERROR_NONE;
237
238 SuccessOrExit(error = mEncoder.WriteUint64(otLinkRawGetRadioTime(mInstance)));
239
240 exit:
241 return error;
242 }
243
HandlePropertySet(void)244 template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MAC_SRC_MATCH_ENABLED>(void)
245 {
246 otError error = OT_ERROR_NONE;
247
248 SuccessOrExit(error = mDecoder.ReadBool(mSrcMatchEnabled));
249
250 error = otLinkRawSrcMatchEnable(mInstance, mSrcMatchEnabled);
251
252 exit:
253 return error;
254 }
255
HandlePropertySet(void)256 template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MAC_SRC_MATCH_SHORT_ADDRESSES>(void)
257 {
258 otError error = OT_ERROR_NONE;
259
260 // Clear the list first
261 SuccessOrExit(error = otLinkRawSrcMatchClearShortEntries(mInstance));
262
263 // Loop through the addresses and add them
264 while (mDecoder.GetRemainingLengthInStruct() >= sizeof(uint16_t))
265 {
266 uint16_t shortAddress;
267
268 SuccessOrExit(error = mDecoder.ReadUint16(shortAddress));
269
270 SuccessOrExit(error = otLinkRawSrcMatchAddShortEntry(mInstance, shortAddress));
271 }
272
273 exit:
274 return error;
275 }
276
HandlePropertySet(void)277 template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MAC_SRC_MATCH_EXTENDED_ADDRESSES>(void)
278 {
279 otError error = OT_ERROR_NONE;
280
281 // Clear the list first
282 SuccessOrExit(error = otLinkRawSrcMatchClearExtEntries(mInstance));
283
284 // Loop through the addresses and add them
285 while (mDecoder.GetRemainingLengthInStruct() >= sizeof(otExtAddress))
286 {
287 const otExtAddress *extAddress;
288
289 SuccessOrExit(error = mDecoder.ReadEui64(extAddress));
290
291 SuccessOrExit(error = otLinkRawSrcMatchAddExtEntry(mInstance, extAddress));
292 }
293
294 exit:
295 return error;
296 }
297
HandlePropertyRemove(void)298 template <> otError NcpBase::HandlePropertyRemove<SPINEL_PROP_MAC_SRC_MATCH_SHORT_ADDRESSES>(void)
299 {
300 otError error = OT_ERROR_NONE;
301 uint16_t shortAddress;
302
303 SuccessOrExit(error = mDecoder.ReadUint16(shortAddress));
304
305 error = otLinkRawSrcMatchClearShortEntry(mInstance, shortAddress);
306
307 exit:
308 return error;
309 }
310
HandlePropertyRemove(void)311 template <> otError NcpBase::HandlePropertyRemove<SPINEL_PROP_MAC_SRC_MATCH_EXTENDED_ADDRESSES>(void)
312 {
313 otError error = OT_ERROR_NONE;
314 const otExtAddress *extAddress;
315
316 SuccessOrExit(error = mDecoder.ReadEui64(extAddress));
317 ;
318
319 error = otLinkRawSrcMatchClearExtEntry(mInstance, extAddress);
320
321 exit:
322 return error;
323 }
324
HandlePropertyInsert(void)325 template <> otError NcpBase::HandlePropertyInsert<SPINEL_PROP_MAC_SRC_MATCH_SHORT_ADDRESSES>(void)
326 {
327 otError error = OT_ERROR_NONE;
328 uint16_t shortAddress;
329
330 SuccessOrExit(error = mDecoder.ReadUint16(shortAddress));
331
332 error = otLinkRawSrcMatchAddShortEntry(mInstance, shortAddress);
333
334 exit:
335 return error;
336 }
337
HandlePropertyInsert(void)338 template <> otError NcpBase::HandlePropertyInsert<SPINEL_PROP_MAC_SRC_MATCH_EXTENDED_ADDRESSES>(void)
339 {
340 otError error = OT_ERROR_NONE;
341 const otExtAddress *extAddress = nullptr;
342
343 SuccessOrExit(error = mDecoder.ReadEui64(extAddress));
344
345 error = otLinkRawSrcMatchAddExtEntry(mInstance, extAddress);
346
347 exit:
348 return error;
349 }
350
HandlePropertySet(void)351 template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_PHY_ENABLED>(void)
352 {
353 bool value = false;
354 otError error = OT_ERROR_NONE;
355
356 SuccessOrExit(error = mDecoder.ReadBool(value));
357
358 if (!value)
359 {
360 error = otLinkRawSetReceiveDone(mInstance, nullptr);
361 }
362 else
363 {
364 error = otLinkRawSetReceiveDone(mInstance, &NcpBase::LinkRawReceiveDone);
365 }
366
367 exit:
368 return error;
369 }
370
HandlePropertySet(void)371 template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MAC_15_4_SADDR>(void)
372 {
373 uint16_t shortAddress;
374 otError error = OT_ERROR_NONE;
375
376 SuccessOrExit(error = mDecoder.ReadUint16(shortAddress));
377
378 error = otLinkRawSetShortAddress(mInstance, shortAddress);
379
380 exit:
381 return error;
382 }
383
DecodeStreamRawTxRequest(otRadioFrame & aFrame)384 otError NcpBase::DecodeStreamRawTxRequest(otRadioFrame &aFrame)
385 {
386 otError error;
387 const uint8_t *payloadPtr;
388 uint16_t payloadLen;
389 bool csmaEnable;
390 bool isARetx;
391 bool isHeaderUpdated;
392 bool isSecurityProcessed;
393
394 SuccessOrExit(error = mDecoder.ReadDataWithLen(payloadPtr, payloadLen));
395 VerifyOrExit(payloadLen <= OT_RADIO_FRAME_MAX_SIZE, error = OT_ERROR_PARSE);
396
397 aFrame.mLength = static_cast<uint8_t>(payloadLen);
398 memcpy(aFrame.mPsdu, payloadPtr, aFrame.mLength);
399
400 // Parse the meta data
401
402 // Channel is a required parameter in meta data.
403 SuccessOrExit(error = mDecoder.ReadUint8(aFrame.mChannel));
404
405 // Set the default value for all optional parameters.
406 aFrame.mInfo.mTxInfo.mMaxCsmaBackoffs = OPENTHREAD_CONFIG_MAC_MAX_CSMA_BACKOFFS_DIRECT;
407 aFrame.mInfo.mTxInfo.mMaxFrameRetries = OPENTHREAD_CONFIG_MAC_DEFAULT_MAX_FRAME_RETRIES_DIRECT;
408 aFrame.mInfo.mTxInfo.mCsmaCaEnabled = true;
409 aFrame.mInfo.mTxInfo.mIsHeaderUpdated = false;
410 aFrame.mInfo.mTxInfo.mIsARetx = false;
411 aFrame.mInfo.mTxInfo.mIsSecurityProcessed = false;
412 aFrame.mInfo.mTxInfo.mTxDelay = 0;
413 aFrame.mInfo.mTxInfo.mTxDelayBaseTime = 0;
414
415 // All the next parameters are optional. Note that even if the
416 // decoder fails to parse any of optional parameters we still want to
417 // return `OT_ERROR_NONE` (so `error` is not updated after this
418 // point).
419
420 SuccessOrExit(mDecoder.ReadUint8(aFrame.mInfo.mTxInfo.mMaxCsmaBackoffs));
421 SuccessOrExit(mDecoder.ReadUint8(aFrame.mInfo.mTxInfo.mMaxFrameRetries));
422 SuccessOrExit(mDecoder.ReadBool(csmaEnable));
423 SuccessOrExit(mDecoder.ReadBool(isHeaderUpdated));
424 SuccessOrExit(mDecoder.ReadBool(isARetx));
425 SuccessOrExit(mDecoder.ReadBool(isSecurityProcessed));
426 SuccessOrExit(mDecoder.ReadUint32(aFrame.mInfo.mTxInfo.mTxDelay));
427 SuccessOrExit(mDecoder.ReadUint32(aFrame.mInfo.mTxInfo.mTxDelayBaseTime));
428 aFrame.mInfo.mTxInfo.mCsmaCaEnabled = csmaEnable;
429 aFrame.mInfo.mTxInfo.mIsHeaderUpdated = isHeaderUpdated;
430 aFrame.mInfo.mTxInfo.mIsARetx = isARetx;
431 aFrame.mInfo.mTxInfo.mIsSecurityProcessed = isSecurityProcessed;
432
433 exit:
434 return error;
435 }
436
HandlePropertySet_SPINEL_PROP_STREAM_RAW(uint8_t aHeader)437 otError NcpBase::HandlePropertySet_SPINEL_PROP_STREAM_RAW(uint8_t aHeader)
438 {
439 otError error = OT_ERROR_NONE;
440 otRadioFrame *frame;
441
442 VerifyOrExit(otLinkRawIsEnabled(mInstance), error = OT_ERROR_INVALID_STATE);
443
444 frame = otLinkRawGetTransmitBuffer(mInstance);
445 VerifyOrExit(frame != nullptr, error = OT_ERROR_NO_BUFS);
446
447 SuccessOrExit(error = DecodeStreamRawTxRequest(*frame));
448
449 // Cache the transaction ID for async response
450 mCurTransmitTID = SPINEL_HEADER_GET_TID(aHeader);
451
452 // Pass frame to the radio layer. Note, this fails if we
453 // haven't enabled raw stream or are already transmitting.
454 error = otLinkRawTransmit(mInstance, &NcpBase::LinkRawTransmitDone);
455
456 exit:
457
458 if (error == OT_ERROR_NONE)
459 {
460 // Don't do anything here yet. We will complete the transaction when we get a transmit done callback
461 }
462 else
463 {
464 error = WriteLastStatusFrame(aHeader, ThreadErrorToSpinelStatus(error));
465 }
466
467 return error;
468 }
469
HandlePropertySet(void)470 template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_RCP_MAC_KEY>(void)
471 {
472 otError error = OT_ERROR_NONE;
473 uint8_t keyIdMode;
474 uint8_t keyId;
475 uint16_t keySize;
476 const uint8_t *prevKey;
477 const uint8_t *currKey;
478 const uint8_t *nextKey;
479
480 SuccessOrExit(error = mDecoder.ReadUint8(keyIdMode));
481 VerifyOrExit(keyIdMode == Mac::Frame::kKeyIdMode1, error = OT_ERROR_INVALID_ARGS);
482
483 SuccessOrExit(error = mDecoder.ReadUint8(keyId));
484
485 SuccessOrExit(error = mDecoder.ReadDataWithLen(prevKey, keySize));
486 VerifyOrExit(keySize == sizeof(otMacKey), error = OT_ERROR_INVALID_ARGS);
487
488 SuccessOrExit(error = mDecoder.ReadDataWithLen(currKey, keySize));
489 VerifyOrExit(keySize == sizeof(otMacKey), error = OT_ERROR_INVALID_ARGS);
490
491 SuccessOrExit(error = mDecoder.ReadDataWithLen(nextKey, keySize));
492 VerifyOrExit(keySize == sizeof(otMacKey), error = OT_ERROR_INVALID_ARGS);
493
494 error =
495 otLinkRawSetMacKey(mInstance, keyIdMode, keyId, reinterpret_cast<const otMacKey *>(prevKey),
496 reinterpret_cast<const otMacKey *>(currKey), reinterpret_cast<const otMacKey *>(nextKey));
497
498 exit:
499 return error;
500 }
501
HandlePropertySet(void)502 template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_RCP_MAC_FRAME_COUNTER>(void)
503 {
504 otError error = OT_ERROR_NONE;
505 uint32_t frameCounter;
506
507 SuccessOrExit(error = mDecoder.ReadUint32(frameCounter));
508
509 error = otLinkRawSetMacFrameCounter(mInstance, frameCounter);
510
511 exit:
512 return error;
513 }
514
515 #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
HandlePropertySet(void)516 template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_RCP_ENH_ACK_PROBING>(void)
517 {
518 otError error = OT_ERROR_NONE;
519 uint16_t shortAddress;
520 const otExtAddress *extAddress;
521 otLinkMetrics linkMetrics = {false, false, false, false, false};
522
523 SuccessOrExit(error = mDecoder.ReadUint16(shortAddress));
524 SuccessOrExit(error = mDecoder.ReadEui64(extAddress));
525 SuccessOrExit(error = DecodeLinkMetrics(&linkMetrics, /* aAllowPduCount */ true));
526
527 error = otPlatRadioConfigureEnhAckProbing(mInstance, linkMetrics, shortAddress, extAddress);
528
529 exit:
530 return error;
531 }
532 #endif
533
534 } // namespace Ncp
535 } // namespace ot
536
537 #endif // OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
538