Skip to content

Commit 5d3173d

Browse files
committed
[thread-direct] add MAC-layer Thread Direct wake initiator and listener hooks
Introduces the core MAC-layer behavior for both the Wake Initiator (WI) and Wake Listener (WL) roles. Wake Initiator: `WakeupTxScheduler` now builds and transmits Thread Direct Wake Commands using `GenerateThreadDirectWakeCommand()` Wake Listener: `Mac::HandleWakeupFrame` replaces its earlier `kErrorNotImplemented` stub with the full receive path: security verification via the wake key, and dispatch of `OT_THREAD_DIRECT_EVENT_WAKE_RECEIVED` through `DirectHandler`. `DirectHandler` centralizes the Thread Direct event callback and SCA (SLW/RAM) parameter storage.
1 parent cb1d8b3 commit 5d3173d

33 files changed

Lines changed: 1549 additions & 452 deletions

examples/platforms/utils/mac_frame.cpp

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -318,11 +318,7 @@ otError otMacFrameProcessTransmitSecurity(otRadioFrame *aFrame, otRadioContext *
318318
uint32_t frameCounter;
319319
bool processKeyId;
320320

321-
processKeyId =
322-
#if OPENTHREAD_CONFIG_THREAD_DIRECT_WAKE_INITIATOR_ENABLE
323-
otMacFrameIsKeyIdMode2(aFrame) ||
324-
#endif
325-
otMacFrameIsKeyIdMode1(aFrame);
321+
processKeyId = otMacFrameIsKeyIdMode1(aFrame);
326322

327323
VerifyOrExit(otMacFrameIsSecurityEnabled(aFrame) && processKeyId && !aFrame->mInfo.mTxInfo.mIsSecurityProcessed);
328324

@@ -455,3 +451,23 @@ bool otMacFrameSrcAddrMatchCslReceiverPeer(const otRadioFrame *aFrame, const otR
455451
exit:
456452
return matches;
457453
}
454+
455+
#if OPENTHREAD_CONFIG_THREAD_DIRECT_WAKE_INITIATOR_ENABLE
456+
457+
bool otMacFrameIsTdWakeCommand(otRadioFrame *aFrame)
458+
{
459+
uint8_t keyId;
460+
461+
if (!otMacFrameIsCommand(aFrame) || otMacFrameIsAckRequested(aFrame) || !otMacFrameIsSecurityEnabled(aFrame) ||
462+
!otMacFrameIsKeyIdMode1(aFrame))
463+
{
464+
return false;
465+
}
466+
467+
keyId = otMacFrameGetKeyId(aFrame);
468+
469+
return keyId == OT_MAC_FRAME_WAKE_KEY_INDEX ||
470+
(keyId >= OT_MAC_FRAME_GUEST_WAKE_KEY_INDEX_MIN && keyId <= OT_MAC_FRAME_GUEST_WAKE_KEY_INDEX_MAX);
471+
}
472+
473+
#endif // OPENTHREAD_CONFIG_THREAD_DIRECT_WAKE_INITIATOR_ENABLE

examples/platforms/utils/mac_frame.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,21 @@ otError otMacFrameProcessTransmitSecurity(otRadioFrame *aFrame, otRadioContext *
401401
*/
402402
bool otMacFrameSrcAddrMatchCslReceiverPeer(const otRadioFrame *aFrame, const otRadioContext *aRadioContext);
403403

404+
#if OPENTHREAD_CONFIG_THREAD_DIRECT_WAKE_INITIATOR_ENABLE
405+
/**
406+
* Tell if @p aFrame is a Thread Direct Wake Command frame.
407+
*
408+
* A TD Wake Command is a MAC Command with no ACK request and wake key index 129 or 130-192
409+
* in the Auxiliary Security Header (stamped by the stack before transmit).
410+
*
411+
* @param[in] aFrame A pointer to the frame.
412+
*
413+
* @retval true The frame is a TD Wake Command.
414+
* @retval false The frame is not a TD Wake Command.
415+
*/
416+
bool otMacFrameIsTdWakeCommand(otRadioFrame *aFrame);
417+
#endif
418+
404419
#ifdef __cplusplus
405420
} // extern "C"
406421
#endif

include/openthread/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ source_set("openthread") {
132132
"tcp.h",
133133
"tcp_ext.h",
134134
"thread.h",
135+
"thread_direct.h",
135136
"thread_ftd.h",
136137
"trel.h",
137138
"udp.h",

include/openthread/dataset.h

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ typedef struct otOperationalDatasetComponents
207207
bool mIsPskcPresent; ///< TRUE if PSKc is present, FALSE otherwise.
208208
bool mIsSecurityPolicyPresent; ///< TRUE if Security Policy is present, FALSE otherwise.
209209
bool mIsChannelMaskPresent; ///< TRUE if Channel Mask is present, FALSE otherwise.
210-
bool mIsWakeupChannelPresent; ///< TRUE if Wake-up Channel is present, FALSE otherwise.
210+
bool mIsWakeupChannelPresent; ///< TRUE if Wake Channel is present, FALSE otherwise.
211211
} otOperationalDatasetComponents;
212212

213213
/**
@@ -227,20 +227,20 @@ typedef struct otTimestamp
227227
*/
228228
typedef struct otOperationalDataset
229229
{
230-
otTimestamp mActiveTimestamp; ///< Active Timestamp
231-
otTimestamp mPendingTimestamp; ///< Pending Timestamp
232-
otNetworkKey mNetworkKey; ///< Network Key
233-
otNetworkName mNetworkName; ///< Network Name
234-
otExtendedPanId mExtendedPanId; ///< Extended PAN ID
235-
otMeshLocalPrefix mMeshLocalPrefix; ///< Mesh Local Prefix
236-
uint32_t mDelay; ///< Delay Timer
237-
otPanId mPanId; ///< PAN ID
238-
uint16_t mChannel; ///< Channel
239-
uint16_t mWakeupChannel; ///< Wake-up Channel
240-
otPskc mPskc; ///< PSKc
241-
otSecurityPolicy mSecurityPolicy; ///< Security Policy
242-
otChannelMask mChannelMask; ///< Channel Mask
243-
otOperationalDatasetComponents mComponents; ///< Specifies which components are set in the Dataset.
230+
otTimestamp mActiveTimestamp; ///< Active Timestamp
231+
otTimestamp mPendingTimestamp; ///< Pending Timestamp
232+
otNetworkKey mNetworkKey; ///< Network Key
233+
otNetworkName mNetworkName; ///< Network Name
234+
otExtendedPanId mExtendedPanId; ///< Extended PAN ID
235+
otMeshLocalPrefix mMeshLocalPrefix; ///< Mesh Local Prefix
236+
uint32_t mDelay; ///< Delay Timer
237+
otPanId mPanId; ///< PAN ID
238+
uint16_t mChannel; ///< Channel
239+
uint16_t mWakeupChannel; ///< Wake Channel (always 20; see OPENTHREAD_CONFIG_THREAD_DIRECT_DEFAULT_WAKE_CHANNEL)
240+
otPskc mPskc; ///< PSKc
241+
otSecurityPolicy mSecurityPolicy; ///< Security Policy
242+
otChannelMask mChannelMask; ///< Channel Mask
243+
otOperationalDatasetComponents mComponents; ///< Specifies which components are set in the Dataset.
244244
} otOperationalDataset;
245245

246246
/**
@@ -303,7 +303,7 @@ typedef enum otMeshcopTlvType
303303
OT_MESHCOP_TLV_SCAN_DURATION = 56, ///< meshcop Scan Duration TLV
304304
OT_MESHCOP_TLV_ENERGY_LIST = 57, ///< meshcop Energy List TLV
305305
OT_MESHCOP_TLV_THREAD_DOMAIN_NAME = 59, ///< meshcop Thread Domain Name TLV
306-
OT_MESHCOP_TLV_WAKEUP_CHANNEL = 74, ///< meshcop Wake-up Channel TLV
306+
OT_MESHCOP_TLV_WAKEUP_CHANNEL = 74, ///< meshcop Wake Channel TLV (always 20 for Thread Direct)
307307
OT_MESHCOP_TLV_ADMITTER_STATE = 90, ///< meshcop Admitter State TLV
308308
OT_MESHCOP_TLV_ENROLLER_ID = 91, ///< meshcop Enroller ID TLV
309309
OT_MESHCOP_TLV_ENROLLER_MODE = 92, ///< meshcop Enroller Mode TLV

include/openthread/instance.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ extern "C" {
5252
*
5353
* @note This number versions both OpenThread platform and user APIs.
5454
*/
55-
#define OPENTHREAD_API_VERSION (608)
55+
#define OPENTHREAD_API_VERSION (609)
5656

5757
/**
5858
* @addtogroup api-instance

include/openthread/thread_direct.h

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
/*
2+
* Copyright (c) 2025, 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 defines the OpenThread API for Thread Direct,
32+
* a MAC-layer peer-to-peer link between Thread devices.
33+
*/
34+
35+
#ifndef OPENTHREAD_THREAD_DIRECT_H_
36+
#define OPENTHREAD_THREAD_DIRECT_H_
37+
38+
#include <stdbool.h>
39+
#include <stdint.h>
40+
41+
#include <openthread/error.h>
42+
#include <openthread/instance.h>
43+
#include <openthread/platform/radio.h>
44+
45+
#ifdef __cplusplus
46+
extern "C" {
47+
#endif
48+
49+
/**
50+
* @addtogroup api-thread-direct
51+
*
52+
* @brief
53+
* This module includes functions for Thread Direct.
54+
*
55+
* @{
56+
*
57+
*/
58+
59+
/**
60+
* Represents a 16-byte guest Wake Key provisioned out-of-band (key indices 130-192).
61+
*/
62+
typedef struct otThreadDirectWakeKey
63+
{
64+
uint8_t m8[16]; ///< Key material (16 bytes).
65+
} otThreadDirectWakeKey;
66+
67+
/**
68+
* Represents the state of an established Thread Direct peer.
69+
*
70+
* The @p mWake* fields are populated on OT_THREAD_DIRECT_EVENT_WAKE_RECEIVED;
71+
* the @p mSlw* and @p mTd* fields are populated on LINKED/UNLINKED events.
72+
*/
73+
typedef struct otThreadDirectPeerInfo
74+
{
75+
otExtAddress mExtAddress; ///< Peer extended address.
76+
uint16_t mTdShortAddress; ///< TD short address [0xFE00, 0xFFFE], or OT_RADIO_INVALID_SHORT_ADDR.
77+
uint16_t mSlwPeriodSlots; ///< Peer's SLW period in 160 us slots (0 = not configured).
78+
uint16_t mSlwPhaseSlots; ///< Peer's SLW phase in 160 us slots.
79+
uint16_t mSupervisionIntervalMs; ///< Supervision interval from TD Link Command, in milliseconds.
80+
uint8_t mServicesBitmap; ///< Services bitmap: bit 0 = peer has SRP server.
81+
82+
/* Fields below are valid only for OT_THREAD_DIRECT_EVENT_WAKE_RECEIVED. */
83+
uint8_t mWakeType; ///< Wake type: 0=link, 1=power-outage, 2=connectionless.
84+
uint32_t mWakeRvTimeUs; ///< Rendezvous time (us from end-of-frame to connection window open).
85+
uint8_t mWakeRetryCount; ///< Retry count from the Wake Frame (number of WI retries).
86+
uint8_t mWakeRetryInterval; ///< Retry interval (units of WI wake interval).
87+
} otThreadDirectPeerInfo;
88+
89+
/**
90+
* Represents a Thread Direct link event.
91+
*/
92+
typedef enum otThreadDirectEvent
93+
{
94+
OT_THREAD_DIRECT_EVENT_LINK_FAILED = 0, ///< Wake attempt failed (timeout or authentication failure).
95+
OT_THREAD_DIRECT_EVENT_WAKE_RECEIVED = 1, ///< WL received a TD Wake Command from a WI peer.
96+
} otThreadDirectEvent;
97+
98+
/**
99+
* Pointer to a function called on Thread Direct link events.
100+
*
101+
* @param[in] aEvent The event type.
102+
* @param[in] aPeerInfo Peer information at the time of the event. May be NULL for LINK_FAILED
103+
* if no peer entry was established.
104+
* @param[in] aContext Application-specific context pointer passed to
105+
* otThreadDirectSetEventCallback().
106+
*/
107+
typedef void (*otThreadDirectEventCallback)(otThreadDirectEvent aEvent,
108+
const otThreadDirectPeerInfo *aPeerInfo,
109+
void *aContext);
110+
111+
/**
112+
* Registers a callback for Thread Direct link events.
113+
*
114+
* Valid for both WI and WL roles. A subsequent call replaces any previously
115+
* registered callback. Pass NULL to clear.
116+
*
117+
* Requires `OPENTHREAD_CONFIG_THREAD_DIRECT_WAKE_INITIATOR_ENABLE` or
118+
* `OPENTHREAD_CONFIG_THREAD_DIRECT_WAKE_LISTENER_ENABLE`.
119+
*
120+
* @param[in] aInstance The OpenThread instance.
121+
* @param[in] aCallback The callback function pointer, or NULL to clear.
122+
* @param[in] aContext Application-specific context pointer passed to @p aCallback.
123+
*/
124+
void otThreadDirectSetEventCallback(otInstance *aInstance, otThreadDirectEventCallback aCallback, void *aContext);
125+
126+
/**
127+
* Thread Direct wake frame type, carried in the Wake Frame payload.
128+
*
129+
* The numeric values match the wire encoding.
130+
*/
131+
typedef enum
132+
{
133+
OT_THREAD_DIRECT_WAKE_TYPE_LINK = 0, ///< Wake Frame requesting TD link establishment.
134+
OT_THREAD_DIRECT_WAKE_TYPE_POWER_OUTAGE = 1, ///< Wake Frame signaling a power outage event.
135+
OT_THREAD_DIRECT_WAKE_TYPE_CONNECTIONLESS = 2, ///< Connectionless Wake Frame (no link establishment).
136+
} otThreadDirectWakeType;
137+
138+
/**
139+
* Starts a Thread Direct wake burst targeting @p aExtAddress.
140+
*
141+
* Transmits Wake Frames of type @p aWakeType at @p aIntervalUs for @p aDurationMs.
142+
* When @p aWakeType is OT_THREAD_DIRECT_WAKE_TYPE_LINK, a connection window is opened
143+
* after the burst; the registered event callback (see otThreadDirectSetEventCallback())
144+
* fires OT_THREAD_DIRECT_EVENT_LINK_FAILED if the window expires without a response.
145+
*
146+
* Pass @p aIntervalUs = 0 to use OPENTHREAD_CONFIG_THREAD_DIRECT_WAKE_INTERVAL_US.
147+
* Pass @p aDurationMs = 0 to use OPENTHREAD_CONFIG_THREAD_DIRECT_WAKE_DURATION_MS.
148+
*
149+
* Wake Frames are secured with the key identified by @p aKeyIndex. Pass 0 or 129 for
150+
* the network-derived Wake Key (HMAC-SHA256(NetworkKey, "Thread-Wake")). Pass an index
151+
* in [130, 192] to use a guest Wake Key previously provisioned via
152+
* otThreadDirectSetGuestWakeKey().
153+
*
154+
* @param[in] aInstance The OpenThread instance.
155+
* @param[in] aExtAddress Extended address of the Wake Listener.
156+
* @param[in] aWakeType Wake frame type.
157+
* @param[in] aIntervalUs Inter-frame interval in us (0 = default).
158+
* @param[in] aDurationMs Wake burst duration in ms (0 = default).
159+
* @param[in] aKeyIndex Key index: 0 or 129 for the network-derived Wake Key;
160+
* [130, 192] to use a provisioned guest Wake Key.
161+
*
162+
* @retval OT_ERROR_NONE Wake burst started.
163+
* @retval OT_ERROR_INVALID_ARGS @p aExtAddress is NULL, or @p aIntervalUs, @p aDurationMs,
164+
* or @p aKeyIndex is out of range.
165+
* @retval OT_ERROR_INVALID_STATE A wake burst is already in progress, or @p aKeyIndex is a
166+
* guest index for which no key has been provisioned.
167+
*
168+
* Requires `OPENTHREAD_CONFIG_THREAD_DIRECT_WAKE_INITIATOR_ENABLE`.
169+
*/
170+
otError otThreadDirectWakeup(otInstance *aInstance,
171+
const otExtAddress *aExtAddress,
172+
otThreadDirectWakeType aWakeType,
173+
uint16_t aIntervalUs,
174+
uint16_t aDurationMs,
175+
uint8_t aKeyIndex);
176+
177+
/**
178+
* Initiates Thread Direct link teardown to the peer identified by @p aExtAddress.
179+
* Sends a TD frame carrying an empty SCA LTV (SCA teardown).
180+
*
181+
* @param[in] aInstance The OpenThread instance.
182+
* @param[in] aExtAddress Extended address of the peer to unlink.
183+
*
184+
* @retval OT_ERROR_NONE Teardown initiated.
185+
* @retval OT_ERROR_NOT_FOUND No established link to @p aExtAddress.
186+
* @retval OT_ERROR_NOT_IMPLEMENTED Feature is not implemented.
187+
*/
188+
otError otThreadDirectUnlink(otInstance *aInstance, const otExtAddress *aExtAddress);
189+
190+
/**
191+
* Enables or disables Wake Listener mode.
192+
*
193+
* When enabled the WL periodically calls otPlatRadioReceiveAt() on Wake Channel 20
194+
* according to the configured listen interval and duration.
195+
*
196+
* @param[in] aInstance The OpenThread instance.
197+
* @param[in] aEnable TRUE to enable WL listen mode; FALSE to disable.
198+
*
199+
* @retval OT_ERROR_NONE Mode updated.
200+
*
201+
* Requires `OPENTHREAD_CONFIG_THREAD_DIRECT_WAKE_LISTENER_ENABLE`.
202+
*/
203+
otError otThreadDirectWakeListenerEnable(otInstance *aInstance, bool aEnable);
204+
205+
/**
206+
* Returns whether WL wake channel listening is currently active.
207+
*
208+
* Requires `OPENTHREAD_CONFIG_THREAD_DIRECT_WAKE_LISTENER_ENABLE`.
209+
*
210+
* @param[in] aInstance The OpenThread instance.
211+
*
212+
* @returns TRUE if the WL is listening for TD Wake Commands, FALSE otherwise.
213+
*/
214+
bool otThreadDirectIsWakeListenerEnabled(otInstance *aInstance);
215+
216+
/**
217+
* Returns whether a WI wake burst is currently in progress.
218+
*
219+
* TRUE covers both the active TX burst phase and the connection window that
220+
* immediately follows, during which the WI waits for a TD Link Command from
221+
* the WL.
222+
*
223+
* Requires `OPENTHREAD_CONFIG_THREAD_DIRECT_WAKE_INITIATOR_ENABLE`.
224+
*
225+
* @param[in] aInstance The OpenThread instance.
226+
*
227+
* @returns TRUE if a wake burst or connection window is active, FALSE otherwise.
228+
*/
229+
bool otThreadDirectIsWakeBurstActive(otInstance *aInstance);
230+
231+
/**
232+
* @}
233+
*
234+
*/
235+
236+
#ifdef __cplusplus
237+
} // extern "C"
238+
#endif
239+
240+
#endif // OPENTHREAD_THREAD_DIRECT_H_

src/core/BUILD.gn

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,7 @@ openthread_core_files = [
377377
"api/tcp_api.cpp",
378378
"api/tcp_ext_api.cpp",
379379
"api/thread_api.cpp",
380+
"api/thread_direct_api.cpp",
380381
"api/thread_ftd_api.cpp",
381382
"api/trel_api.cpp",
382383
"api/udp_api.cpp",
@@ -521,6 +522,8 @@ openthread_core_files = [
521522
"mac/channel_mask.hpp",
522523
"mac/data_poll_handler.cpp",
523524
"mac/data_poll_handler.hpp",
525+
"mac/direct_handler.cpp",
526+
"mac/direct_handler.hpp",
524527
"mac/data_poll_sender.cpp",
525528
"mac/data_poll_sender.hpp",
526529
"mac/link_raw.cpp",

0 commit comments

Comments
 (0)