1#!/usr/bin/env python3
2#
3#  Copyright (c) 2020, The OpenThread Authors.
4#  All rights reserved.
5#
6#  Redistribution and use in source and binary forms, with or without
7#  modification, are permitted provided that the following conditions are met:
8#  1. Redistributions of source code must retain the above copyright
9#     notice, this list of conditions and the following disclaimer.
10#  2. Redistributions in binary form must reproduce the above copyright
11#     notice, this list of conditions and the following disclaimer in the
12#     documentation and/or other materials provided with the distribution.
13#  3. Neither the name of the copyright holder nor the
14#     names of its contributors may be used to endorse or promote products
15#     derived from this software without specific prior written permission.
16#
17#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20#  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21#  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22#  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23#  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24#  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25#  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26#  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27#  POSSIBILITY OF SUCH DAMAGE.
28#
29
30import unittest
31
32from config import ADDRESS_TYPE
33from mle import LinkMetricsSubTlvType, TlvType
34from pktverify import consts
35from pktverify.null_field import nullField
36from pktverify.packet_verifier import PacketVerifier
37
38import thread_cert
39
40LEADER = 1
41SED_1 = 2
42SSED_1 = 3
43
44SERIES_ID = 1
45SERIES_ID_2 = 2
46POLL_PERIOD = 2000  # 2s
47
48
49class LowPower_7_2_01_ForwardTrackingSeries(thread_cert.TestCase):
50    TOPOLOGY = {
51        LEADER: {
52            'version': '1.2',
53            'name': 'LEADER',
54            'mode': 'rdn',
55            'allowlist': [SED_1, SSED_1],
56        },
57        SED_1: {
58            'version': '1.2',
59            'name': 'SED_1',
60            'mode': '-',
61            'allowlist': [LEADER],
62        },
63        SSED_1: {
64            'version': '1.2',
65            'name': 'SSED_1',
66            'mode': '-',
67            'allowlist': [LEADER],
68        }
69    }
70    """All nodes are created with default configurations"""
71
72    def test(self):
73        self.nodes[LEADER].start()
74        self.simulator.go(5)
75        self.assertEqual(self.nodes[LEADER].get_state(), 'leader')
76
77        self.nodes[SED_1].set_pollperiod(POLL_PERIOD)
78        self.nodes[SED_1].start()
79        self.simulator.go(5)
80        self.assertEqual(self.nodes[SED_1].get_state(), 'child')
81
82        self.nodes[SSED_1].set_csl_period(consts.CSL_DEFAULT_PERIOD)
83        self.nodes[SSED_1].start()
84        self.simulator.go(5)
85        self.assertEqual(self.nodes[SSED_1].get_state(), 'child')
86
87        leader_addr = self.nodes[LEADER].get_ip6_address(ADDRESS_TYPE.LINK_LOCAL)
88
89        # Step 3 - Verify connectivity by instructing each device to send an ICMPv6 Echo Request to the DUT
90        self.assertTrue(self.nodes[SED_1].ping(leader_addr, timeout=POLL_PERIOD / 1000))
91        self.assertTrue(self.nodes[SSED_1].ping(leader_addr))
92        self.simulator.go(5)
93
94        # Step 4 - SED_1 requests a Forward Tracking Series by sending a Link Metrics Management Request
95        # Forward Series Flags = 0x04:
96        # - Bit 2 = 1, MAC Data Request
97        # - Bits 0,1 & 3-7 = 0
98        # Concatenation of Link Metric Type ID Flags = 0x00:
99        # - Item1: (0)(1)(000)(000) = 0x40
100        # -- E = 0
101        # -- L = 1
102        # -- Type/Average Enum = 0 (count)
103        # -- Metrics Enum = 0 (count)
104        self.nodes[SED_1].link_metrics_mgmt_req_forward_tracking_series(leader_addr, SERIES_ID, 'r', 'p')
105
106        self.simulator.go(5)
107
108        # Step 6 - Configures SED_1 to send MAC Data Request every 2 seconds and wait for 30 seconds
109        self.simulator.go(30)
110
111        # Step 7 - SED_1 sends an MLE Data Request to retrieve aggregated Forward Series Results
112        self.nodes[SED_1].link_metrics_query_forward_tracking_series(leader_addr, SERIES_ID)
113
114        self.simulator.go(5)
115
116        # Step 9 - SED_1 clears the Forward Tracking Series
117        # Forward Series Flags = 0x00:
118        # - Bits 0-7 = 0
119        # Concatenation of Link Metric Type ID Flags = NULL:
120        self.nodes[SED_1].link_metrics_mgmt_req_forward_tracking_series(leader_addr, SERIES_ID, 'X', '')
121
122        self.simulator.go(5)
123
124        # Step 16 - SSED_1 requests a Forward Tracking Series by sending a Link Metrics Management Request
125        # Forward Series Flags = 0x02:
126        # - Bit 1 = 1, (all Link Layer Data Frames)
127        # - Bits 0, 2-7 = 0
128        # Concatenation of Link Metric Type ID Flags = 0x0a:
129        # - Item1: (0)(0)(001)(010) = 0x0a
130        # -- E = 0
131        # -- L = 0
132        # -- Type/Average Enum = 1 (Exponential Moving Average)
133        # -- Metrics Enum = 2 (Link Margin)
134        self.nodes[SSED_1].link_metrics_mgmt_req_forward_tracking_series(leader_addr, SERIES_ID_2, 'd', 'm')
135
136        self.simulator.go(5)
137
138        # Step 18 - SSED_1 sends MAC Data packet every 1 second for 30 seconds
139        # Note: As the api to send MAC Data frame hasn't been implemented, we send MLE Link Probe message here
140        for i in range(30):
141            self.nodes[SSED_1].link_metrics_send_link_probe(leader_addr, SERIES_ID_2, 1)
142            self.simulator.go(1)
143
144        # Step 19 - SSED_1 sends an MLE Data Request to retrieve aggregated Forward Series Results
145        self.nodes[SSED_1].link_metrics_query_forward_tracking_series(leader_addr, SERIES_ID_2)
146
147        self.simulator.go(5)
148
149        # Step 21 - SSED_1 clears the Forward Series Link Metrics
150        # Forward Series Flags = 0x00:
151        # - Bits 0-7 = 0
152        # Concatenation of Link Metric Type ID Flags = NULL
153        self.nodes[SSED_1].link_metrics_mgmt_req_forward_tracking_series(leader_addr, SERIES_ID_2, 'X', '')
154
155        self.simulator.go(5)
156
157        # Step 23 - SSED_1 sends an MLE Data Request to retrieve aggregated Forward Series Results
158        self.nodes[SSED_1].link_metrics_query_forward_tracking_series(leader_addr, SERIES_ID_2)
159
160        self.simulator.go(5)
161
162    def verify(self, pv):
163        pkts = pv.pkts
164        pv.summary.show()
165        LEADER = pv.vars['LEADER']
166        SED_1 = pv.vars['SED_1']
167        SSED_1 = pv.vars['SSED_1']
168
169        # Step 3 - The DUT MUST send ICMPv6 Echo Responses to both SED1 & SSED1
170        pkts.filter_wpan_src64(LEADER) \
171            .filter_wpan_dst64(SED_1) \
172            .filter_ping_reply() \
173            .must_next()
174        pkts.filter_wpan_src64(LEADER) \
175            .filter_wpan_dst64(SSED_1) \
176            .filter_ping_reply() \
177            .must_next()
178
179        # Step 4 - SED_1 requests a Forward Series Link Metrics by sending a Link Metrics Management Request
180        # Forward Series Flags = 0x04:
181        # - Bit 2 = 1, MAC Data Request
182        # - Bits 0,1 & 3-7 = 0
183        # Concatenation of Link Metric Type ID Flags = 0x40:
184        # - Item1: (0)(1)(000)(000) = 0x40
185        # -- E = 0
186        # -- L = 1
187        # -- Type/Average Enum = 0 (count)
188        # -- Metrics Enum = 0 (count)
189        pkts.filter_wpan_src64(SED_1) \
190            .filter_wpan_dst64(LEADER) \
191            .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_REQUEST) \
192            .filter(lambda p: p.mle.tlv.link_sub_tlv == LinkMetricsSubTlvType.FORWARD_PROBING_REGISTRATION) \
193            .filter(lambda p: 4 in p.mle.tlv.link_forward_series)  \
194            .filter(lambda p: consts.LINK_METRICS_TYPE_AVERAGE_ENUM_COUNT in p.mle.tlv.metric_type_id_flags.type) \
195            .filter(lambda p: consts.LINK_METRICS_METRIC_TYPE_ENUM_PDU_COUNT in p.mle.tlv.metric_type_id_flags.metric) \
196            .filter(lambda p: 1 in p.mle.tlv.metric_type_id_flags.l) \
197            .must_next()
198
199        # Step 5 - Leader responds with a Link Metrics Management Response
200        pkts.filter_wpan_src64(LEADER) \
201            .filter_wpan_dst64(SED_1) \
202            .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_RESPONSE) \
203            .filter(lambda p: p.mle.tlv.link_status_sub_tlv == consts.LINK_METRICS_STATUS_SUCCESS) \
204            .must_next()
205
206        # Step 8 - Leader responds with an MLE Data Response
207        pkts.filter_wpan_src64(LEADER) \
208            .filter_wpan_dst64(SED_1) \
209            .filter_mle_cmd(consts.MLE_DATA_RESPONSE) \
210            .filter(lambda p: TlvType.LINK_METRICS_REPORT in p.mle.tlv.type) \
211            .filter(lambda p: consts.LINK_METRICS_TYPE_AVERAGE_ENUM_COUNT in p.mle.tlv.metric_type_id_flags.type) \
212            .filter(lambda p: consts.LINK_METRICS_METRIC_TYPE_ENUM_PDU_COUNT in p.mle.tlv.metric_type_id_flags.metric) \
213            .must_next()
214
215        # Step 9 - SED_1 clears the Forward Series Link Metrics
216        # Forward Series Flags = 0x00:
217        # - Bits 0-7 = 0
218        # Concatenation of Link Metric Type ID Flags = NULL:
219        pkts.filter_wpan_src64(SED_1) \
220            .filter_wpan_dst64(LEADER) \
221            .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_REQUEST) \
222            .filter(lambda p: p.mle.tlv.link_sub_tlv == LinkMetricsSubTlvType.FORWARD_PROBING_REGISTRATION) \
223            .filter(lambda p: 0 in p.mle.tlv.link_forward_series) \
224            .filter(lambda p: p.mle.tlv.metric_type_id_flags.type is nullField) \
225            .filter(lambda p: p.mle.tlv.metric_type_id_flags.metric is nullField) \
226            .must_next()
227
228        # Step 10 - Leader responds with a Link Metrics Management Response
229        pkts.filter_wpan_src64(LEADER) \
230            .filter_wpan_dst64(SED_1) \
231            .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_RESPONSE) \
232            .filter(lambda p: p.mle.tlv.link_status_sub_tlv == consts.LINK_METRICS_STATUS_SUCCESS) \
233            .must_next()
234
235        # Step 16 - SSED_1 requests a Forward Series Link Metrics by sending a Link Metrics Management Request
236        # Forward Series Flags = 0x02:
237        # - Bit 1 = 1, (all Link Layer Data Frames)
238        # - Bits 0, 2-7 = 0
239        # Concatenation of Link Metric Type ID Flags = 0x0a:
240        # - Item1: (0)(0)(001)(010) = 0x0a
241        # -- E = 0
242        # -- L = 0
243        # -- Type/Average Enum = 1 (Exponential Moving Average)
244        # -- Metrics Enum = 2 (Link Margin)
245        pkts.filter_wpan_src64(SSED_1) \
246            .filter_wpan_dst64(LEADER) \
247            .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_REQUEST) \
248            .filter(lambda p: p.mle.tlv.link_sub_tlv == LinkMetricsSubTlvType.FORWARD_PROBING_REGISTRATION) \
249            .filter(lambda p: 2 in p.mle.tlv.link_forward_series) \
250            .filter(lambda p: consts.LINK_METRICS_TYPE_AVERAGE_ENUM_EXPONENTIAL in p.mle.tlv.metric_type_id_flags.type) \
251            .filter(lambda p: consts.LINK_METRICS_METRIC_TYPE_ENUM_LINK_MARGIN in p.mle.tlv.metric_type_id_flags.metric) \
252            .must_next()
253
254        # Step 17 - Leader responds with a Link Metrics Management Response
255        pkts.filter_wpan_src64(LEADER) \
256            .filter_wpan_dst64(SSED_1) \
257            .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_RESPONSE) \
258            .filter(lambda p: p.mle.tlv.link_status_sub_tlv == consts.LINK_METRICS_STATUS_SUCCESS) \
259            .must_next()
260
261        # Step 19 - SSED_1 sends an MLE Data Request to retrieve aggregated Forward Series results
262        # - TLV Request TLV(Link Metrics Report TLV specified
263        # - Link Metrics Query TLV
264        # -- Link Metrics Query ID Sub-TLV
265        # --- Query ID: (ID set in step 16)
266        # - Verify that the Link Metrics Query Options Sub-TLV is NOT present
267        pkts.filter_wpan_src64(SSED_1) \
268            .filter_wpan_dst64(LEADER) \
269            .filter_mle_cmd(consts.MLE_DATA_REQUEST) \
270            .filter(lambda p: TlvType.LINK_METRICS_QUERY in p.mle.tlv.type) \
271            .filter(lambda p: p.mle.tlv.query_id == SERIES_ID_2) \
272            .must_next()
273
274        # Step 20 - Leader MUST reply to SSED_1 with an MLE Data Response with the following:
275        # - Link Metrics Report TLV
276        # -- Link Metrics Report Sub-TLV
277        # --- Metric Type ID Flags
278        # ---- Type / Average Enum = 1 (Exponential Moving Average)
279        # ---- Metric Enum = 2 (Link Margin)
280        # --- Value (1 bytes)
281        pkts.filter_wpan_src64(LEADER) \
282            .filter_wpan_dst64(SSED_1) \
283            .filter_mle_cmd(consts.MLE_DATA_RESPONSE) \
284            .filter_mle_has_tlv(TlvType.LINK_METRICS_REPORT) \
285            .filter(lambda p: p.mle.tlv.link_sub_tlv == LinkMetricsSubTlvType.LINK_METRICS_REPORT) \
286            .filter(lambda p: consts.LINK_METRICS_TYPE_AVERAGE_ENUM_EXPONENTIAL in p.mle.tlv.metric_type_id_flags.type) \
287            .filter(lambda p: consts.LINK_METRICS_METRIC_TYPE_ENUM_LINK_MARGIN in p.mle.tlv.metric_type_id_flags.metric) \
288            .must_next()
289
290        # Step 21 - SSED_1 clears the Forward Series Link Metrics
291        # Forward Series Flags = 0x00:
292        # - Bits 0-7 = 0
293        # Concatenation of Link Metric Type ID Flags = NULL
294        pkts.filter_wpan_src64(SSED_1) \
295            .filter_wpan_dst64(LEADER) \
296            .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_REQUEST) \
297            .filter(lambda p: p.mle.tlv.link_sub_tlv == LinkMetricsSubTlvType.FORWARD_PROBING_REGISTRATION) \
298            .filter(lambda p: 0 in p.mle.tlv.link_forward_series) \
299            .filter(lambda p: p.mle.tlv.metric_type_id_flags.type is nullField) \
300            .filter(lambda p: p.mle.tlv.metric_type_id_flags.metric is nullField) \
301            .must_next()
302
303        # Step 22 - Leader responds with a Link Metrics Management Response
304        pkts.filter_wpan_src64(LEADER) \
305            .filter_wpan_dst64(SSED_1) \
306            .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_RESPONSE) \
307            .filter(lambda p: p.mle.tlv.link_status_sub_tlv == consts.LINK_METRICS_STATUS_SUCCESS) \
308            .must_next()
309
310        # Step 23 - SSED_1 sends an MLE Data Request to retrieve aggregated Forward Series results
311        # - TLV Request TLV(Link Metrics Report TLV specified
312        # - Link Metrics Query TLV
313        # -- Link Metrics Query ID Sub-TLV
314        # --- Query ID: (ID set in step 16)
315        # This is a negative step
316        pkts.filter_wpan_src64(SSED_1) \
317            .filter_wpan_dst64(LEADER) \
318            .filter_mle_cmd(consts.MLE_DATA_REQUEST) \
319            .filter(lambda p: TlvType.LINK_METRICS_QUERY in p.mle.tlv.type) \
320            .filter(lambda p: p.mle.tlv.query_id == SERIES_ID_2) \
321            .must_next()
322
323        # Step 24 - Leader MUST reply to SSED_1 with an MLE Data Response with the following:
324        # - Link Metrics Report TLV
325        # -- Link Metrics Status Sub-TLV
326        # --- Status = 3 (Series ID not recognized)
327        pkts.filter_wpan_src64(LEADER) \
328            .filter_wpan_dst64(SSED_1) \
329            .filter_mle_cmd(consts.MLE_DATA_RESPONSE) \
330            .filter_mle_has_tlv(TlvType.LINK_METRICS_REPORT) \
331            .filter(lambda p: p.mle.tlv.link_sub_tlv == LinkMetricsSubTlvType.LINK_METRICS_STATUS) \
332            .filter(lambda p: p.mle.tlv.link_status_sub_tlv == consts.LINK_METRICS_STATUS_SERIES_ID_NOT_RECOGNIZED) \
333            .must_next()
334
335
336if __name__ == '__main__':
337    unittest.main()
338