xref: /aosp_15_r20/external/openthread/tests/unit/test_link_quality.cpp (revision cfb92d1480a9e65faed56933e9c12405f45898b4)
1 /*
2  *  Copyright (c) 2016, 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 #include "test_platform.h"
30 #include "test_util.h"
31 
32 #include "common/array.hpp"
33 #include "common/code_utils.hpp"
34 #include "thread/link_metrics.hpp"
35 #include "thread/link_quality.hpp"
36 
37 namespace ot {
38 
39 static Instance *sInstance;
40 
41 enum
42 {
43     kMaxRssValue = 0,
44     kMinRssValue = -128,
45 
46     kStringBuffferSize = 80,
47 
48     kRssAverageMaxDiff = 16,
49     kNumRssAdds        = 300,
50 
51     kRawAverageBitShift = 3,
52     kRawAverageMultiple = (1 << kRawAverageBitShift),
53     kRawAverageBitMask  = (1 << kRawAverageBitShift) - 1,
54 };
55 
56 #define MIN_RSS(_rss1, _rss2) (((_rss1) < (_rss2)) ? (_rss1) : (_rss2))
57 #define MAX_RSS(_rss1, _rss2) (((_rss1) < (_rss2)) ? (_rss2) : (_rss1))
58 #define ABS(value) (((value) >= 0) ? (value) : -(value))
59 
60 // This struct contains RSS values and test data for checking link quality info class.
61 struct RssTestData
62 {
63     const int8_t *mRssList;             // Array of RSS values.
64     size_t        mRssListSize;         // Size of RSS list.
65     uint8_t       mExpectedLinkQuality; // Expected final link quality value.
66 };
67 
68 int8_t sNoiseFloor = -100; // dBm
69 
70 // Check and verify the raw average RSS value to match the value from GetAverage().
VerifyRawRssValue(int8_t aAverage,uint16_t aRawValue)71 void VerifyRawRssValue(int8_t aAverage, uint16_t aRawValue)
72 {
73     if (aAverage != Radio::kInvalidRssi)
74     {
75         VerifyOrQuit(aAverage == -static_cast<int16_t>((aRawValue + (kRawAverageMultiple / 2)) >> kRawAverageBitShift),
76                      "Raw value does not match the average.");
77     }
78     else
79     {
80         VerifyOrQuit(aRawValue == 0, "Raw value does not match the average.");
81     }
82 }
83 
84 // This function prints the values in the passed in link info instance. It is invoked as the final step in test-case.
PrintOutcome(LinkQualityInfo & aLinkInfo)85 void PrintOutcome(LinkQualityInfo &aLinkInfo) { printf("%s -> PASS \n", aLinkInfo.ToInfoString().AsCString()); }
86 
TestLinkQualityData(RssTestData aRssData)87 void TestLinkQualityData(RssTestData aRssData)
88 {
89     LinkQualityInfo linkInfo;
90     int8_t          rss, ave, min, max;
91     size_t          i;
92 
93     sInstance = testInitInstance();
94     VerifyOrQuit(sInstance != nullptr);
95     linkInfo.Init(*sInstance);
96 
97     printf("- - - - - - - - - - - - - - - - - -\n");
98     linkInfo.Clear();
99     min = kMinRssValue;
100     max = kMaxRssValue;
101 
102     for (i = 0; i < aRssData.mRssListSize; i++)
103     {
104         rss = aRssData.mRssList[i];
105         min = MIN_RSS(rss, min);
106         max = MAX_RSS(rss, max);
107         linkInfo.AddRss(rss);
108         VerifyOrQuit(linkInfo.GetLastRss() == rss);
109         ave = linkInfo.GetAverageRss();
110         VerifyOrQuit(ave >= min, "GetAverageRss() is smaller than min value.");
111         VerifyOrQuit(ave <= max, "GetAverageRss() is larger than min value");
112         VerifyRawRssValue(linkInfo.GetAverageRss(), linkInfo.GetAverageRssRaw());
113         printf("%02u) AddRss(%4d): ", static_cast<unsigned int>(i), rss);
114         PrintOutcome(linkInfo);
115     }
116 
117     VerifyOrQuit(linkInfo.GetLinkQuality() == aRssData.mExpectedLinkQuality);
118 }
119 
120 // Check and verify the raw average RSS value to match the value from GetAverage().
VerifyRawRssValue(RssAverager & aRssAverager)121 void VerifyRawRssValue(RssAverager &aRssAverager)
122 {
123     int8_t   average  = aRssAverager.GetAverage();
124     uint16_t rawValue = aRssAverager.GetRaw();
125 
126     if (average != Radio::kInvalidRssi)
127     {
128         VerifyOrQuit(average == -static_cast<int16_t>((rawValue + (kRawAverageMultiple / 2)) >> kRawAverageBitShift),
129                      "Raw value does not match the average.");
130     }
131     else
132     {
133         VerifyOrQuit(rawValue == 0, "Raw value does not match the average.");
134     }
135 }
136 
137 // This function prints the values in the passed link info instance. It is invoked as the final step in test-case.
PrintOutcome(RssAverager & aRssAverager)138 void PrintOutcome(RssAverager &aRssAverager) { printf("%s -> PASS\n", aRssAverager.ToString().AsCString()); }
139 
GetRandomRss(void)140 int8_t GetRandomRss(void)
141 {
142     uint32_t value;
143 
144     value = rand() % 128;
145     return -static_cast<int8_t>(value);
146 }
147 
TestRssAveraging(void)148 void TestRssAveraging(void)
149 {
150     RssAverager  rssAverager;
151     int8_t       rss, rss2, ave;
152     int16_t      diff;
153     size_t       i, j, k;
154     const int8_t rssValues[] = {kMinRssValue, -70, -40, -41, -10, kMaxRssValue};
155     int16_t      sum;
156 
157     //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
158     // Values after initialization/reset.
159 
160     rssAverager.Clear();
161 
162     printf("\nAfter Clear: ");
163     VerifyOrQuit(rssAverager.GetAverage() == Radio::kInvalidRssi, "Initial value from GetAverage() is incorrect.");
164     VerifyRawRssValue(rssAverager);
165     PrintOutcome(rssAverager);
166 
167     //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
168     // Adding a single value
169     rss = -70;
170     printf("AddRss(%d): ", rss);
171     IgnoreError(rssAverager.Add(rss));
172     VerifyOrQuit(rssAverager.GetAverage() == rss, "GetAverage() failed after a single AddRss().");
173     VerifyRawRssValue(rssAverager);
174     PrintOutcome(rssAverager);
175 
176     //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
177     // Clear
178 
179     printf("Clear(): ");
180     rssAverager.Clear();
181     VerifyOrQuit(rssAverager.GetAverage() == Radio::kInvalidRssi, "GetAverage() after Clear() is incorrect.");
182     VerifyRawRssValue(rssAverager);
183     PrintOutcome(rssAverager);
184 
185     //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
186     // Adding the same value many times.
187 
188     printf("- - - - - - - - - - - - - - - - - -\n");
189 
190     for (j = 0; j < sizeof(rssValues); j++)
191     {
192         rssAverager.Clear();
193         rss = rssValues[j];
194         printf("AddRss(%4d) %d times: ", rss, kNumRssAdds);
195 
196         for (i = 0; i < kNumRssAdds; i++)
197         {
198             IgnoreError(rssAverager.Add(rss));
199             VerifyOrQuit(rssAverager.GetAverage() == rss, "GetAverage() returned incorrect value.");
200             VerifyRawRssValue(rssAverager);
201         }
202 
203         PrintOutcome(rssAverager);
204     }
205 
206     //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
207     // Adding two RSS values:
208 
209     printf("- - - - - - - - - - - - - - - - - -\n");
210 
211     for (j = 0; j < sizeof(rssValues); j++)
212     {
213         rss = rssValues[j];
214 
215         for (k = 0; k < sizeof(rssValues); k++)
216         {
217             if (k == j)
218             {
219                 continue;
220             }
221 
222             rss2 = rssValues[k];
223             rssAverager.Clear();
224             IgnoreError(rssAverager.Add(rss));
225             IgnoreError(rssAverager.Add(rss2));
226             printf("AddRss(%4d), AddRss(%4d): ", rss, rss2);
227             VerifyOrQuit(rssAverager.GetAverage() == ((rss + rss2) >> 1), "GetAverage() returned incorrect value.");
228             VerifyRawRssValue(rssAverager);
229             PrintOutcome(rssAverager);
230         }
231     }
232 
233     //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
234     // Adding one value many times and a different value once:
235 
236     printf("- - - - - - - - - - - - - - - - - -\n");
237 
238     for (j = 0; j < sizeof(rssValues); j++)
239     {
240         rss = rssValues[j];
241 
242         for (k = 0; k < sizeof(rssValues); k++)
243         {
244             if (k == j)
245             {
246                 continue;
247             }
248 
249             rss2 = rssValues[k];
250             rssAverager.Clear();
251 
252             for (i = 0; i < kNumRssAdds; i++)
253             {
254                 IgnoreError(rssAverager.Add(rss));
255             }
256 
257             IgnoreError(rssAverager.Add(rss2));
258             printf("AddRss(%4d) %d times, AddRss(%4d): ", rss, kNumRssAdds, rss2);
259             ave = rssAverager.GetAverage();
260             VerifyOrQuit(ave >= MIN_RSS(rss, rss2), "GetAverage() returned incorrect value.");
261             VerifyOrQuit(ave <= MAX_RSS(rss, rss2), "GetAverage() returned incorrect value.");
262             VerifyRawRssValue(rssAverager);
263             PrintOutcome(rssAverager);
264         }
265     }
266 
267     //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
268     // Adding two alternating values many times:
269 
270     printf("- - - - - - - - - - - - - - - - - -\n");
271 
272     for (j = 0; j < sizeof(rssValues); j++)
273     {
274         rss = rssValues[j];
275 
276         for (k = 0; k < sizeof(rssValues); k++)
277         {
278             if (k == j)
279             {
280                 continue;
281             }
282 
283             rss2 = rssValues[k];
284             rssAverager.Clear();
285 
286             for (i = 0; i < kNumRssAdds; i++)
287             {
288                 IgnoreError(rssAverager.Add(rss));
289                 IgnoreError(rssAverager.Add(rss2));
290                 ave = rssAverager.GetAverage();
291                 VerifyOrQuit(ave >= MIN_RSS(rss, rss2), "GetAverage() is smaller than min value.");
292                 VerifyOrQuit(ave <= MAX_RSS(rss, rss2), "GetAverage() is larger than min value.");
293                 diff = ave;
294                 diff -= (rss + rss2) >> 1;
295                 VerifyOrQuit(ABS(diff) <= kRssAverageMaxDiff, "GetAverage() is incorrect");
296                 VerifyRawRssValue(rssAverager);
297             }
298 
299             printf("[AddRss(%4d),  AddRss(%4d)] %d times: ", rss, rss2, kNumRssAdds);
300             PrintOutcome(rssAverager);
301         }
302     }
303 
304     //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
305     // For the first 8 values the average should be the arithmetic mean.
306 
307     printf("- - - - - - - - - - - - - - - - - -\n");
308 
309     for (i = 0; i < 1000; i++)
310     {
311         double mean;
312 
313         rssAverager.Clear();
314         sum = 0;
315 
316         printf("\n");
317 
318         for (j = 1; j <= 8; j++)
319         {
320             rss = GetRandomRss();
321             IgnoreError(rssAverager.Add(rss));
322             sum += rss;
323             mean = static_cast<double>(sum) / static_cast<double>(j);
324             VerifyOrQuit(ABS(rssAverager.GetAverage() - mean) < 1, "Average does not match the arithmetic mean!");
325             VerifyRawRssValue(rssAverager);
326             printf("AddRss(%4d) sum=%-5d, mean=%-8.2f RssAverager=", rss, sum, mean);
327             PrintOutcome(rssAverager);
328         }
329     }
330 }
331 
TestLinkQualityCalculations(void)332 void TestLinkQualityCalculations(void)
333 {
334     const int8_t      rssList1[] = {-81, -80, -79, -78, -76, -80, -77, -75, -77, -76, -77, -74};
335     const RssTestData rssData1   = {
336           rssList1,         // mRssList
337           sizeof(rssList1), // mRssListSize
338           3                 // mExpectedLinkQuality
339     };
340 
341     const int8_t      rssList2[] = {-90, -80, -85};
342     const RssTestData rssData2   = {
343           rssList2,         // mRssList
344           sizeof(rssList2), // mRssListSize
345           2                 // mExpectedLinkQuality
346     };
347 
348     const int8_t      rssList3[] = {-95, -96, -98, -99, -100, -100, -98, -99, -100, -100, -100, -100, -100};
349     const RssTestData rssData3   = {
350           rssList3,         // mRssList
351           sizeof(rssList3), // mRssListSize
352           0                 // mExpectedLinkQuality
353     };
354 
355     const int8_t      rssList4[] = {-75, -100, -100, -100, -100, -100, -95, -92, -93, -94, -93, -93};
356     const RssTestData rssData4   = {
357           rssList4,         // mRssList
358           sizeof(rssList4), // mRssListSize
359           1                 // mExpectedLinkQuality
360     };
361 
362     TestLinkQualityData(rssData1);
363     TestLinkQualityData(rssData2);
364     TestLinkQualityData(rssData3);
365     TestLinkQualityData(rssData4);
366 }
367 
TestSuccessRateTracker(void)368 void TestSuccessRateTracker(void)
369 {
370     SuccessRateTracker rateTracker;
371     uint16_t           sampleCount;
372 
373     const uint16_t kMaxSamples = 5000;
374 
375     const uint16_t kMaxRate       = SuccessRateTracker::kMaxRateValue;
376     const double   kMaxError      = 1.0; // Max permitted error in percentage
377     const uint16_t kWeightLimit[] = {64, 128, 256, 300, 512, 810, 900};
378 
379     printf("\nTesting SuccessRateTracker\n");
380 
381     rateTracker.Clear();
382 
383     VerifyOrQuit(rateTracker.GetSuccessRate() == kMaxRate, "SuccessRateTracker: Initial value incorrect");
384     VerifyOrQuit(rateTracker.GetFailureRate() == 0, "SuccessRateTracker: Initial value incorrect");
385 
386     // Adding all success
387     for (sampleCount = 1; sampleCount < kMaxSamples; sampleCount++)
388     {
389         rateTracker.AddSample(true, sampleCount);
390 
391         VerifyOrQuit(rateTracker.GetSuccessRate() == kMaxRate, "SuccessRateTracker: incorrect rate all success case");
392         VerifyOrQuit(rateTracker.GetFailureRate() == 0, "SuccessRateTracker: incorrect rate in all success case");
393     }
394 
395     rateTracker.Clear();
396     VerifyOrQuit(rateTracker.GetSuccessRate() == kMaxRate, "SuccessRateTracker: Rate incorrect after reset");
397     VerifyOrQuit(rateTracker.GetFailureRate() == 0, "SuccessRateTracker: Rate incorrect after reset");
398 
399     // Adding all failures
400     for (sampleCount = 1; sampleCount < kMaxRate; sampleCount++)
401     {
402         rateTracker.AddSample(false, sampleCount);
403 
404         VerifyOrQuit(rateTracker.GetSuccessRate() == 0, "SuccessRateTracker: rate incorrect all failure case");
405         VerifyOrQuit(rateTracker.GetFailureRate() == kMaxRate,
406                      "SuccessRateTracker: rate incorrect in all failure case");
407     }
408 
409     // Adding success/failure at different rates and checking the RateTracker rate for every sample
410 
411     for (uint16_t testRound = 0; testRound < GetArrayLength(kWeightLimit) * 2; testRound++)
412     {
413         uint16_t weightLimit;
414         bool     reverseLogic;
415         double   maxDiff = 0;
416 
417         // Reverse the logic (add success instead of failure) on even test rounds
418         reverseLogic = ((testRound % 2) == 0);
419 
420         // Select a different weight limit based on the current test round
421         weightLimit = kWeightLimit[testRound / 2];
422 
423         printf("TestRound %02d, weightLimit %3d, reverseLogic %d ", testRound, weightLimit, reverseLogic);
424 
425         for (uint16_t period = 1; period < 101; period++)
426         {
427             uint16_t failureCount = 0;
428 
429             rateTracker.Clear();
430 
431             for (sampleCount = 1; sampleCount < kMaxSamples; sampleCount++)
432             {
433                 double   expectedRate;
434                 double   failureRate;
435                 double   diff;
436                 bool     isSuccess = ((sampleCount % period) == 0);
437                 uint16_t weight;
438 
439                 if (reverseLogic)
440                 {
441                     isSuccess = !isSuccess;
442                 }
443 
444                 weight = sampleCount;
445 
446                 if (weight > weightLimit)
447                 {
448                     weight = weightLimit;
449                 }
450 
451                 rateTracker.AddSample(isSuccess, weight);
452 
453                 if (!isSuccess)
454                 {
455                     failureCount++;
456                 }
457 
458                 // Calculate the failure rate from rateTracker and expected rate.
459 
460                 failureRate  = static_cast<double>(rateTracker.GetFailureRate()) * 100.0 / kMaxRate; // in percent
461                 expectedRate = static_cast<double>(failureCount) * 100.0 / sampleCount;              // in percent
462 
463                 diff = failureRate - expectedRate;
464                 diff = ABS(diff);
465 
466                 VerifyOrQuit(diff <= kMaxError, "SuccessRateTracker: rate does not match expected value");
467 
468                 if (diff > maxDiff)
469                 {
470                     maxDiff = diff;
471                 }
472             }
473         }
474 
475         printf(" MaxDiff = %.3f%%-> PASS\n", maxDiff);
476     }
477 }
478 
479 #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE || OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
480 
481 class UnitTester
482 {
483 public:
TestLinkMetricsScaling(void)484     static void TestLinkMetricsScaling(void)
485     {
486         printf("\nTestLinkMetricsScaling\n");
487 
488         // Test Link Margin scaling from [0,130] -> [0, 255]
489 
490         for (uint8_t linkMargin = 0; linkMargin <= 130; linkMargin++)
491         {
492             double  scaled     = 255.0 / 130.0 * linkMargin;
493             uint8_t scaledAsU8 = static_cast<uint8_t>(scaled + 0.5);
494 
495             printf("\nLinkMargin : %-3u -> Scaled : %.1f (rounded:%u)", linkMargin, scaled, scaledAsU8);
496 
497             VerifyOrQuit(LinkMetrics::ScaleLinkMarginToRawValue(linkMargin) == scaledAsU8);
498             VerifyOrQuit(LinkMetrics::ScaleRawValueToLinkMargin(scaledAsU8) == linkMargin);
499         }
500 
501         VerifyOrQuit(LinkMetrics::ScaleLinkMarginToRawValue(131) == 255);
502         VerifyOrQuit(LinkMetrics::ScaleLinkMarginToRawValue(150) == 255);
503         VerifyOrQuit(LinkMetrics::ScaleLinkMarginToRawValue(255) == 255);
504 
505         // Test RSSI scaling from [-130, 0] -> [0, 255]
506 
507         for (int8_t rssi = -128; rssi <= 0; rssi++)
508         {
509             double  scaled     = 255.0 / 130.0 * (rssi + 130.0);
510             uint8_t scaledAsU8 = static_cast<uint8_t>(scaled + 0.5);
511 
512             printf("\nRSSI : %-3d -> Scaled :%.1f (rounded:%u)", rssi, scaled, scaledAsU8);
513 
514             VerifyOrQuit(LinkMetrics::ScaleRssiToRawValue(rssi) == scaledAsU8);
515             VerifyOrQuit(LinkMetrics::ScaleRawValueToRssi(scaledAsU8) == rssi);
516         }
517 
518         VerifyOrQuit(LinkMetrics::ScaleRssiToRawValue(1) == 255);
519         VerifyOrQuit(LinkMetrics::ScaleRssiToRawValue(10) == 255);
520         VerifyOrQuit(LinkMetrics::ScaleRssiToRawValue(127) == 255);
521 
522         // Test corner case of ScaleRawValueToRssi
523         for (uint8_t rawValue = 0; rawValue < 2; rawValue++)
524         {
525             int8_t rssi = LinkMetrics::ScaleRawValueToRssi(rawValue);
526             printf("\nRaw Value: %u -> RSSI : %-3d", rawValue, rssi);
527         }
528     }
529 };
530 
531 #endif
532 
533 } // namespace ot
534 
main(void)535 int main(void)
536 {
537     ot::TestRssAveraging();
538     ot::TestLinkQualityCalculations();
539     ot::TestSuccessRateTracker();
540 #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE || OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
541     ot::UnitTester::TestLinkMetricsScaling();
542 #endif
543 
544     printf("\nAll tests passed\n");
545     return 0;
546 }
547