avl_tree.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2019 Theodoros Ntakouris <zarkopafilis@gmail.com>
3  */
4 
5 #include <gtest/gtest.h>
9 
10 using uavcan::AvlTree;
11 
12 struct Entry {
13  int key;
14  int payload;
15 
16  bool operator<(const Entry& other) const {
17  return this->key < other.key;
18  }
19 
20  bool operator>(const Entry& other) const {
21  return this->key > other.key;
22  }
23 
24  bool operator==(const Entry& other) const {
25  return this->key == other.key;
26  }
27 };
28 
29 // OOM-Unsafe
30 inline Entry* makeEntry(uavcan::PoolAllocator<64 * 24, 64>* allocator, int key, int payload) {
31  void* praw = allocator->allocate(sizeof(Entry));
32 
33  Entry* e = new (praw) Entry();
34  UAVCAN_ASSERT(e);
35 
36  e->key = key;
37  e->payload = payload;
38  return e;
39 }
40 
41 inline bool matchPostOrder(Entry* expected[], AvlTree<Entry>* tree) {
42  int count = 0;
43  bool res = true;
44 
45  tree->walkPostOrder([expected, &count, &res](Entry*& in) {
46  bool res_ = in == expected[count];
47  res &= res_;
48  count++;
49  });
50 
51  return res;
52 }
53 
54 /* Basic sanity checks */
55 TEST(AvlTree, Sanity) {
56  uavcan::PoolAllocator<64 * 24, 64> pool; // 4 (x2) entries capacity
57 
58  AvlTree<Entry> tree(pool, 99999);
59  EXPECT_TRUE(tree.isEmpty());
60  EXPECT_EQ(0, pool.getNumUsedBlocks());
61 
62  Entry* e1 = makeEntry(&pool, 1, 1);
63  Entry* e2 = makeEntry(&pool, 2, 2);
64  Entry* e3 = makeEntry(&pool, 3, 3);
65  Entry* e4 = makeEntry(&pool, 4, 4);
66 
67  EXPECT_EQ(4, pool.getNumUsedBlocks());
68 
69  tree.insert(e1);
70  EXPECT_TRUE(tree.contains(e1));
71  EXPECT_EQ(e1, tree.max());
72  EXPECT_EQ(1, tree.getSize());
73  EXPECT_EQ(5, pool.getNumUsedBlocks());
74 
75  tree.removeEntry(e1);
76  EXPECT_FALSE(tree.contains(e1));
77  EXPECT_EQ(UAVCAN_NULLPTR, tree.max());
78  EXPECT_EQ(0, tree.getSize());
79  EXPECT_EQ(4, pool.getNumUsedBlocks());
80 
81  // Won't break if asked to remove data that do not exist
82  tree.removeEntry(e1);
83  EXPECT_FALSE(tree.contains(e1));
84  EXPECT_EQ(UAVCAN_NULLPTR, tree.max());
85  EXPECT_EQ(0, tree.getSize());
86  EXPECT_EQ(4, pool.getNumUsedBlocks());
87 
88  /*
89  * Insert e2 - e1 - e3 - e4
90  */
91 
92  tree.insert(e2);
93  EXPECT_TRUE(tree.contains(e2));
94  EXPECT_EQ(e2, tree.max());
95  EXPECT_EQ(1, tree.getSize());
96  EXPECT_EQ(5, pool.getNumUsedBlocks());
97 
98  tree.insert(e1);
99  EXPECT_TRUE(tree.contains(e1));
100  EXPECT_EQ(e2, tree.max());
101  EXPECT_EQ(2, tree.getSize());
102  EXPECT_EQ(6, pool.getNumUsedBlocks());
103 
104  tree.insert(e3);
105  EXPECT_TRUE(tree.contains(e3));
106  EXPECT_EQ(e3, tree.max());
107  EXPECT_EQ(3, tree.getSize());
108  EXPECT_EQ(7, pool.getNumUsedBlocks());
109 
110  tree.insert(e4);
111  EXPECT_TRUE(tree.contains(e4));
112  EXPECT_EQ(e4, tree.max());
113  EXPECT_EQ(4, tree.getSize());
114  EXPECT_EQ(8, pool.getNumUsedBlocks());
115 
116  /*
117  * Remove e2 - e4
118  */
119 
120  tree.removeEntry(e2);
121  EXPECT_TRUE(tree.contains(e1));
122  EXPECT_FALSE(tree.contains(e2));
123  EXPECT_TRUE(tree.contains(e3));
124  EXPECT_TRUE(tree.contains(e4));
125  EXPECT_EQ(e4, tree.max());
126  EXPECT_EQ(3, tree.getSize());
127  EXPECT_EQ(7, pool.getNumUsedBlocks());
128 
129  tree.removeEntry(e4);
130  EXPECT_TRUE(tree.contains(e1));
131  EXPECT_TRUE(tree.contains(e3));
132  EXPECT_FALSE(tree.contains(e4));
133  EXPECT_EQ(e3, tree.max());
134  EXPECT_EQ(2, tree.getSize());
135  EXPECT_EQ(6, pool.getNumUsedBlocks());
136 }
137 
141 TEST(AvlTree, Issue254)
142 {
143  constexpr std::size_t block_size = 64;
144  constexpr std::size_t block_count = 24;
146 
147  AvlTree<Entry> tree(pool, 99999);
148 
149  Entry* e1_0 = makeEntry(&pool, 1, 1);
150  Entry* e1_1 = makeEntry(&pool, 1, 1);
151  Entry* e1_2 = makeEntry(&pool, 1, 1);
152  Entry* e1_3 = makeEntry(&pool, 1, 1);
153 
154  tree.insert(e1_0);
155  tree.insert(e1_1);
156  tree.insert(e1_2);
157  tree.insert(e1_3);
158 
159  EXPECT_TRUE(tree.contains(e1_0));
160  EXPECT_TRUE(tree.contains(e1_1));
161  EXPECT_TRUE(tree.contains(e1_2));
162  EXPECT_TRUE(tree.contains(e1_3));
163  EXPECT_EQ(e1_0, tree.max());
164  EXPECT_EQ(4, tree.getSize());
165  EXPECT_EQ(8, pool.getNumUsedBlocks());
166 }
167 
168 /* Test multiple entries with same 'key' */
169 TEST(AvlTree, MultipleEntriesPerKey) {
170  uavcan::PoolAllocator<64 * 24, 64> pool; // 4 (x2) entries capacity
171 
172  AvlTree<Entry> tree(pool, 99999);
173 
174  Entry* e1 = makeEntry(&pool, 1, 1);
175  Entry* e1_1 = makeEntry(&pool, 1, 11);
176  Entry* e1_11 = makeEntry(&pool, 1, 111);
177 
178  Entry* e2 = makeEntry(&pool, 2, 2);
179 
180  /*
181  * Insert 2 entries with same key
182  */
183  tree.insert(e1);
184 
185  tree.insert(e1_1);
186  EXPECT_TRUE(tree.contains(e1));
187  EXPECT_TRUE(tree.contains(e1_1));
188  EXPECT_EQ(e1, tree.max());
189  EXPECT_EQ(2, tree.getSize());
190  EXPECT_EQ(6, pool.getNumUsedBlocks());
191 
192 
193  tree.removeEntry(e1);
194  EXPECT_FALSE(tree.contains(e1));
195  EXPECT_TRUE(tree.contains(e1_1));
196 
197  EXPECT_EQ(e1_1, tree.max());
198  EXPECT_EQ(1, tree.getSize());
199  EXPECT_EQ(5, pool.getNumUsedBlocks());
200 
201  tree.removeEntry(e1);
202 
203  /*
204  * Insert another with higher priority and
205  * test again: removing in the middle and end of queue
206  * */
207  tree.insert(e2);
208 
209  tree.insert(e1);
210  tree.insert(e1_1);
211  tree.insert(e1_11);
212 
213  EXPECT_TRUE(tree.contains(e2));
214  EXPECT_TRUE(tree.contains(e1));
215  EXPECT_TRUE(tree.contains(e1_1));
216  EXPECT_TRUE(tree.contains(e1_11));
217 
218  EXPECT_EQ(e2, tree.max());
219  EXPECT_EQ(4, tree.getSize());
220  EXPECT_EQ(8, pool.getNumUsedBlocks());
221 
222  tree.removeEntry(e2);
223  tree.removeEntry(e1_1); // middle one in node with key == 1
224  EXPECT_FALSE(tree.contains(e2));
225  EXPECT_TRUE(tree.contains(e1));
226  EXPECT_FALSE(tree.contains(e1_1));
227  EXPECT_TRUE(tree.contains(e1_11));
228 
229  EXPECT_EQ(e1, tree.max()); // peeked in the order they were inserted
230  EXPECT_EQ(2, tree.getSize());
231  EXPECT_EQ(6, pool.getNumUsedBlocks());
232 
233  tree.removeEntry(e1_11); // last one in queue
234  EXPECT_EQ(e1, tree.max());
235  EXPECT_FALSE(tree.contains(e1_11));
236 
237  EXPECT_EQ(1, tree.getSize());
238  EXPECT_EQ(5, pool.getNumUsedBlocks());
239 }
240 
241 TEST(AvlTree, FailToAllocateNode) {
242  uavcan::PoolAllocator<64 * 3, 64> pool; // 2 entries + 1 node
243 
244  AvlTree<Entry> tree(pool, 9999);
245 
246  void* praw = pool.allocate(sizeof(Entry));
247 
248  Entry* e1 = new (praw) Entry();
249  e1->key = 1;
250  e1->payload = 1;
251 
252  praw = pool.allocate(sizeof(Entry));
253  Entry* e2 = new (praw) Entry();
254  e2->key = 2;
255  e2->payload = 2;
256 
257  EXPECT_TRUE(tree.insert(e1));
258  EXPECT_EQ(1, tree.getSize());
259 
260  EXPECT_FALSE(tree.insert(e2)); // OOM -- Will print something like 'UAVCAN: AvlTree: OOM -- Can't allocate Node'
261  EXPECT_EQ(1, tree.getSize());
262 }
263 
264 /* Check all possible rotation / balancing cases
265  * Test cases from: https://stackoverflow.com/questions/3955680/how-to-check-if-my-avl-tree-implementation-is-correct
266  * */
267 TEST(AvlTree, AllRotations) {
269 
270  AvlTree<Entry> tree(pool, 99999);
271  EXPECT_TRUE(tree.isEmpty());
272  EXPECT_EQ(0, pool.getNumUsedBlocks());
273 
274  Entry* a = makeEntry(&pool, 1, 1);
275  Entry* b = makeEntry(&pool, 2, 2);
276  Entry* c = makeEntry(&pool, 3, 3);
277  Entry* d = makeEntry(&pool, 4, 4);
278  Entry* e = makeEntry(&pool, 5, 5);
279  Entry* f = makeEntry(&pool, 6, 6);
280  Entry* g = makeEntry(&pool, 7, 7);
281  Entry* h = makeEntry(&pool, 8, 8);
282  Entry* i = makeEntry(&pool, 9, 9);
283  Entry* j = makeEntry(&pool, 10, 10);
284  Entry* k = makeEntry(&pool, 11, 11);
285  Entry* l = makeEntry(&pool, 12, 12);
286 
287  /*
288  * Simple test cases for insert
289  * */
290 
291  EXPECT_TRUE(tree.isEmpty());
292  EXPECT_EQ(12, pool.getNumUsedBlocks());
293  /*
294  * a b
295  \ / \
296  b == 1L ==> a c
297  \
298  c
299  */
300  tree.insert(a);
301  tree.insert(b);
302  tree.insert(c);
303 
304  Entry* match_1l[] = {a, c, b};
305  EXPECT_TRUE(matchPostOrder(match_1l , &tree));
306 
307  tree.removeEntry(a);
308  tree.removeEntry(b);
309  tree.removeEntry(c);
310 
311  EXPECT_TRUE(tree.isEmpty());
312  EXPECT_EQ(12, pool.getNumUsedBlocks());
313  /*
314  *
315  * c b
316  / / \
317  b == 1R ==> a c
318  /
319  a
320  */
321  tree.insert(c);
322  tree.insert(b);
323  tree.insert(a);
324 
325  Entry* match_1r[] = {a,c,b};
326  EXPECT_TRUE(matchPostOrder(match_1r , &tree));
327 
328  tree.removeEntry(c);
329  tree.removeEntry(b);
330  tree.removeEntry(a);
331 
332  EXPECT_TRUE(tree.isEmpty());
333  EXPECT_EQ(12, pool.getNumUsedBlocks());
334  /*
335  *
336  * a b
337  \ / \
338  c == 2L ==> a c
339  /
340  b
341  */
342  tree.insert(a);
343  tree.insert(c);
344  tree.insert(b);
345 
346  Entry* match_2l[] = {a,c,b};
347  EXPECT_TRUE(matchPostOrder(match_2l , &tree));
348 
349  tree.removeEntry(a);
350  tree.removeEntry(c);
351  tree.removeEntry(b);
352 
353  EXPECT_TRUE(tree.isEmpty());
354  EXPECT_EQ(12, pool.getNumUsedBlocks());
355  /*
356  *
357  * c b
358  / / \
359  a == 2R ==> a c
360  \
361  b
362  */
363 
364  tree.insert(c);
365  tree.insert(a);
366  tree.insert(b);
367 
368  Entry* match_2r[] = {a,c,b};
369  EXPECT_TRUE(matchPostOrder(match_2r , &tree));
370 
371  tree.removeEntry(c);
372  tree.removeEntry(a);
373  tree.removeEntry(b);
374 
375  EXPECT_TRUE(tree.isEmpty());
376  EXPECT_EQ(12, pool.getNumUsedBlocks());
377 
378  /*
379  * Simple cases for deletion
380  */
381 
382  /*
383  * b c
384  x \ / \
385  a c == 1L ==> b d
386  \
387  d
388  */
389 
390  tree.insert(b);
391  tree.insert(a);
392  tree.insert(c);
393  tree.insert(d);
394 
395  Entry* match_pre_del_1l[] = {a,d,c,b};
396  EXPECT_TRUE(matchPostOrder(match_pre_del_1l , &tree));
397 
398  tree.removeEntry(a);
399 
400  Entry* match_post_del_1l[] = {b,d,c};
401  EXPECT_TRUE(matchPostOrder(match_post_del_1l , &tree));
402 
403  tree.removeEntry(b);
404  tree.removeEntry(c);
405  tree.removeEntry(d);
406 
407  EXPECT_TRUE(tree.isEmpty());
408  EXPECT_EQ(12, pool.getNumUsedBlocks());
409 
410  /*
411  *
412  * c b
413  / x / \
414  b d == 1R ==> a c
415  /
416  a
417  */
418  tree.insert(c);
419  tree.insert(d);
420  tree.insert(b);
421  tree.insert(a);
422 
423  Entry* match_pre_del_1r[] = {a,b,d,c};
424  EXPECT_TRUE(matchPostOrder(match_pre_del_1r , &tree));
425 
426  tree.removeEntry(d);
427 
428  Entry* match_post_del_1r[] = {a,c,b};
429  EXPECT_TRUE(matchPostOrder(match_post_del_1r , &tree));
430 
431  tree.removeEntry(c);
432  tree.removeEntry(b);
433  tree.removeEntry(a);
434 
435  EXPECT_TRUE(tree.isEmpty());
436  EXPECT_EQ(12, pool.getNumUsedBlocks());
437  /*
438  * b c
439  x \ / \
440  a d == 2L ==> b d
441  /
442  c
443  */
444  tree.insert(b);
445  tree.insert(a);
446  tree.insert(d);
447  tree.insert(c);
448 
449  Entry* match_pre_del_2l[] = {a,c,d,b};
450  EXPECT_TRUE(matchPostOrder(match_pre_del_2l , &tree));
451 
452  tree.removeEntry(a);
453 
454  Entry* match_post_del_2l[] = {b,d,c};
455  EXPECT_TRUE(matchPostOrder(match_post_del_2l , &tree));
456 
457  tree.removeEntry(b);
458  tree.removeEntry(d);
459  tree.removeEntry(c);
460 
461  EXPECT_TRUE(tree.isEmpty());
462  EXPECT_EQ(12, pool.getNumUsedBlocks());
463  /*
464  *
465  * c b
466  / x / \
467  a d == 2R ==> a c
468  \
469  b
470  */
471  tree.insert(c);
472  tree.insert(d);
473  tree.insert(a);
474  tree.insert(b);
475 
476  Entry* match_pre_del_2r[] = {b,a,d,c};
477  EXPECT_TRUE(matchPostOrder(match_pre_del_2r , &tree));
478 
479  tree.removeEntry(d);
480 
481  Entry* match_post_del_2r[] = {a,c,b};
482  EXPECT_TRUE(matchPostOrder(match_post_del_2r , &tree));
483 
484  tree.removeEntry(c);
485  tree.removeEntry(a);
486  tree.removeEntry(b);
487 
488  EXPECT_TRUE(tree.isEmpty());
489  EXPECT_EQ(12, pool.getNumUsedBlocks());
490 
491  /*
492  * More Complex Tests
493  */
494 
495  /*
496  *
497  * c e
498  / \ / \
499  b e == 1R ==> c f
500  x / \ / \ \
501  a d f b d g
502  \
503  g
504  */
505 
506  tree.insert(c);
507  tree.insert(b);
508  tree.insert(e);
509  tree.insert(a);
510  tree.insert(d);
511  tree.insert(f);
512  tree.insert(g);
513  Entry* match_c_pre_del_1r[] = {a,b,d,g,f,e,c};
514  EXPECT_TRUE(matchPostOrder(match_c_pre_del_1r , &tree));
515 
516  tree.removeEntry(a);
517 
518  Entry* match_c_post_del_1r[] = {b,d,c,g,f,e};
519  EXPECT_TRUE(matchPostOrder(match_c_post_del_1r , &tree));
520 
521  tree.removeEntry(c);
522  tree.removeEntry(b);
523  tree.removeEntry(e);
524  tree.removeEntry(d);
525  tree.removeEntry(f);
526  tree.removeEntry(g);
527 
528  EXPECT_TRUE(tree.isEmpty());
529  EXPECT_EQ(12, pool.getNumUsedBlocks());
530 
531  /*
532  *
533  * - e - c
534  / \ / \
535  c f == 1R ==> b e
536  / \ x / / \
537  b d g a d f
538  /
539  a
540  */
541 
542  tree.insert(e);
543  tree.insert(c);
544  tree.insert(f);
545  tree.insert(b);
546  tree.insert(d);
547  tree.insert(g);
548  tree.insert(a);
549 
550  Entry* match_c2_pre_del_1r[] = {a,b,d,c,g,f,e};
551  EXPECT_TRUE(matchPostOrder(match_c2_pre_del_1r , &tree));
552 
553  tree.removeEntry(g);
554 
555  Entry* match_c2_post_del_1r[] = {a,b,d,f,e,c};
556  EXPECT_TRUE(matchPostOrder(match_c2_post_del_1r , &tree));
557 
558  tree.removeEntry(e);
559  tree.removeEntry(c);
560  tree.removeEntry(f);
561  tree.removeEntry(b);
562  tree.removeEntry(d);
563  tree.removeEntry(a);
564 
565  EXPECT_TRUE(tree.isEmpty());
566  EXPECT_EQ(12, pool.getNumUsedBlocks());
567 
568  /*
569  *
570  * - e - —- h —-
571  / \ / \
572  c j - e- j
573  / \ / \ == 2L ==> / \ / \
574  a d h k c g i k
575  x / \ \ / \ / \
576  b g i l a d f l
577  /
578  f
579  */
580 
581  tree.insert(e);
582  tree.insert(c);
583  tree.insert(j);
584  tree.insert(a);
585  tree.insert(d);
586  tree.insert(h);
587  tree.insert(k);
588  tree.insert(b);
589  tree.insert(g);
590  tree.insert(i);
591  tree.insert(l);
592  tree.insert(f);
593 
594  Entry* match_c_pre_del_2l[] = {b,a,d,c,f,g,i,h,l,k,j,e};
595  EXPECT_TRUE(matchPostOrder(match_c_pre_del_2l , &tree));
596 
597  tree.removeEntry(b);
598 
599  Entry* match_c_post_del_2l[] = {a,d,c,f,g,e,i,l,k,j,h};
600  EXPECT_TRUE(matchPostOrder(match_c_post_del_2l , &tree));
601 
602  tree.removeEntry(e);
603  tree.removeEntry(c);
604  tree.removeEntry(j);
605  tree.removeEntry(a);
606  tree.removeEntry(d);
607  tree.removeEntry(h);
608  tree.removeEntry(k);
609  tree.removeEntry(g);
610  tree.removeEntry(i);
611  tree.removeEntry(l);
612  tree.removeEntry(f);
613 
614  EXPECT_TRUE(tree.isEmpty());
615  EXPECT_EQ(12, pool.getNumUsedBlocks());
616  /*
617  *
618  * - h - - e -
619  / \ / \
620  c k c - h -
621  / \ / \ == 2R ==> / \ / \
622  b e i l b d f k
623  / / \ x / \ / \
624  a d f j a g i l
625  \
626  g
627  */
628  tree.insert(h);
629  tree.insert(c);
630  tree.insert(k);
631  tree.insert(b);
632  tree.insert(e);
633  tree.insert(i);
634  tree.insert(l);
635  tree.insert(a);
636  tree.insert(d);
637  tree.insert(f);
638  tree.insert(j);
639  tree.insert(g);
640 
641  Entry* match_c_pre_del_2r[] = {a,b,d,g,f,e,c,j,i,l,k,h};
642  EXPECT_TRUE(matchPostOrder(match_c_pre_del_2r , &tree));
643 
644  tree.removeEntry(j);
645 
646  Entry* match_c_post_del_2r[] = {a,b,d,c,g,f,i,l,k,h,e};
647  EXPECT_TRUE(matchPostOrder(match_c_post_del_2r , &tree));
648 
649  tree.removeEntry(h);
650  tree.removeEntry(c);
651  tree.removeEntry(k);
652  tree.removeEntry(b);
653  tree.removeEntry(e);
654  tree.removeEntry(i);
655  tree.removeEntry(l);
656  tree.removeEntry(a);
657  tree.removeEntry(d);
658  tree.removeEntry(f);
659  tree.removeEntry(g);
660 
661  EXPECT_TRUE(tree.isEmpty());
662  EXPECT_EQ(12, pool.getNumUsedBlocks());
663 }
UAVCAN_NULLPTR
#define UAVCAN_NULLPTR
Definition: libuavcan/libuavcan/include/uavcan/build_config.hpp:51
std::size_t
unsigned long size_t
Definition: coverity_scan_model.cpp:19
dynamic_memory.hpp
uavcan::AvlTree::isEmpty
bool isEmpty() const
Definition: avl_tree.hpp:348
system_clock.hpp
uavcan::AvlTree::removeEntry
void removeEntry(T *data)
Definition: avl_tree.hpp:324
uavcan::AvlTree::walkPostOrder
void walkPostOrder(std::function< void(T *&)> forEach)
Definition: avl_tree.hpp:343
f
f
uavcan::PoolAllocator
Definition: dynamic_memory.hpp:51
uavcan::PoolAllocator::allocate
virtual void * allocate(std::size_t size)
Definition: dynamic_memory.hpp:160
Entry::key
int key
Definition: avl_tree.cpp:13
makeEntry
Entry * makeEntry(uavcan::PoolAllocator< 64 *24, 64 > *allocator, int key, int payload)
Definition: avl_tree.cpp:30
Entry::operator<
bool operator<(const Entry &other) const
Definition: avl_tree.cpp:16
Entry::payload
int payload
Definition: avl_tree.cpp:14
uavcan::AvlTree::max
T * max() const
Definition: avl_tree.hpp:352
d
d
Entry
Definition: avl_tree.cpp:12
uavcan::AvlTree::insert
bool insert(T *data)
Definition: avl_tree.hpp:328
Entry::operator==
bool operator==(const Entry &other) const
Definition: avl_tree.cpp:24
matchPostOrder
bool matchPostOrder(Entry *expected[], AvlTree< Entry > *tree)
Definition: avl_tree.cpp:41
Entry::operator>
bool operator>(const Entry &other) const
Definition: avl_tree.cpp:20
uavcan::AvlTree::contains
bool contains(const T *data) const
Definition: avl_tree.hpp:366
uavcan::AvlTree::getSize
size_t getSize() const
Definition: avl_tree.hpp:339
TEST
TEST(AvlTree, Sanity)
Definition: avl_tree.cpp:55
uavcan::PoolAllocator::getNumUsedBlocks
uint16_t getNumUsedBlocks() const
Definition: dynamic_memory.hpp:85
uavcan::AvlTree
Definition: avl_tree.hpp:22
avl_tree.hpp
UAVCAN_ASSERT
#define UAVCAN_ASSERT(x)
Definition: libuavcan/libuavcan/include/uavcan/build_config.hpp:184


uavcan_communicator
Author(s):
autogenerated on Fri Dec 13 2024 03:10:02