Open Model Railroad Network (OpenMRN)
Loading...
Searching...
No Matches
AliasCache.cxx
Go to the documentation of this file.
1
35
36#include <set>
37
38#include "os/OS.hxx"
39#include "utils/logging.h"
40
41#ifdef GTEST
42#define TEST_CONSISTENCY
43#endif
44
45namespace openlcb
46{
47
48#define CONSTANT 0x1B0CA37ABA9
55{
56 if ((stored & NOT_RESPONDING) == NOT_RESPONDING)
57 {
58 return NOT_RESPONDING;
59 }
60 return stored;
61}
62
63#if defined(TEST_CONSISTENCY)
64extern volatile int consistency_result;
65volatile int consistency_result = 0;
66
68{
69 if (idMap.size() != aliasMap.size())
70 {
71 LOG(INFO, "idmap size != aliasmap size.");
72 return 1;
73 }
74 if (aliasMap.size() == entries)
75 {
76 if (!freeList.empty())
77 {
78 LOG(INFO, "Found freelist entry when map is full.");
79 return 2;
80 }
81 }
82 else
83 {
84 if (freeList.empty())
85 {
86 LOG(INFO, "No freelist entry although map is not full.");
87 return 3;
88 }
89 }
90 if (aliasMap.size() == 0 && (!oldest.empty() || !newest.empty()))
91 {
92 LOG(INFO, "LRU head/tail elements should be null when map is empty.");
93 return 4;
94 }
95 std::set<void *> free_entries;
96 for (PoolIdx p = freeList; !p.empty(); p = p.deref(this)->older_)
97 {
98 Metadata *m = p.deref(this);
99 if (free_entries.count(m))
100 {
101 LOG(INFO, "Duplicate entry on freelist.");
102 return 5;
103 }
104 free_entries.insert(m);
105 }
106 if (free_entries.size() + aliasMap.size() != entries)
107 {
108 LOG(INFO, "Lost some metadata entries.");
109 return 6;
110 }
111 for (auto kv : aliasMap)
112 {
113 if (free_entries.count(kv.deref(this)))
114 {
115 LOG(INFO, "Found an aliasmap entry in the freelist.");
116 return 19;
117 }
118 }
119 for (auto kv : idMap)
120 {
121 if (free_entries.count(kv.deref(this)))
122 {
123 LOG(INFO, "Found an id entry in the freelist.");
124 return 20;
125 }
126 }
127 if (aliasMap.size() == 0)
128 {
129 if (!oldest.empty())
130 {
131 LOG(INFO, "Oldest should be empty when map is empty.");
132 return 7;
133 }
134 if (!newest.empty())
135 {
136 LOG(INFO, "Newest should be empty when map is empty.");
137 return 8;
138 }
139 }
140 else
141 {
142 if (oldest.empty())
143 {
144 LOG(INFO, "Oldest should be nonempty when map is nonempty.");
145 return 9;
146 }
147 if (newest.empty())
148 {
149 LOG(INFO, "Newest should be nonempty when map is nonempty.");
150 return 10;
151 }
152 if (free_entries.count(oldest.deref(this)))
153 {
154 LOG(INFO, "Oldest is on the freelist.");
155 return 11; // oldest is free
156 }
157 if (free_entries.count(newest.deref(this)))
158 {
159 LOG(INFO, "Newest is on the freelist.");
160 return 12; // newest is free
161 }
162 }
163 if (aliasMap.size() == 0)
164 {
165 return 0;
166 }
167 // Check linking.
168 {
169 PoolIdx prev = oldest;
170 unsigned count = 1;
171 if (!prev.deref(this)->older_.empty())
172 {
173 LOG(INFO, "Prev link points to empty.");
174 return 13;
175 }
176 while (!prev.deref(this)->newer_.empty())
177 {
178 auto next = prev.deref(this)->newer_;
179 ++count;
180 if (free_entries.count(next.deref(this)))
181 {
182 LOG(INFO, "Next link points to the freelist.");
183 return 21;
184 }
185 if (next.deref(this)->older_.idx_ != prev.idx_)
186 {
187 LOG(INFO, "Next link broken.");
188 return 14;
189 }
190 prev = next;
191 }
192 if (prev.idx_ != newest.idx_)
193 {
194 LOG(INFO, "Prev link points to newest.");
195 return 18;
196 }
197 if (count != aliasMap.size())
198 {
199 LOG(INFO, "LRU link list length is incorrect.");
200 return 27;
201 }
202 }
203 {
204 PoolIdx next = newest;
205 if (!next.deref(this)->newer_.empty())
206 {
207 LOG(INFO, "Newest has a newer link.");
208 return 15;
209 }
210 while (!next.deref(this)->older_.empty())
211 {
212 auto prev = next.deref(this)->older_;
213 if (free_entries.count(prev.deref(this)))
214 {
215 LOG(INFO, "Prev link points to the freelist.");
216 return 22;
217 }
218 if (prev.deref(this)->newer_.idx_ != next.idx_)
219 {
220 LOG(INFO, "Prev link broken.");
221 return 16;
222 }
223 next = prev;
224 }
225 if (next.idx_ != oldest.idx_)
226 {
227 LOG(INFO, "Next link points to oldest.");
228 return 17;
229 }
230 }
231 for (unsigned i = 0; i < entries; ++i)
232 {
233 if (free_entries.count(pool + i))
234 {
235 continue;
236 }
237 auto *e = pool + i;
238 if (idMap.find(e->get_node_id()) == idMap.end())
239 {
240 LOG(INFO, "Metadata ID is not in the id map.");
241 return 23;
242 }
243 if (idMap.find(e->get_node_id())->idx_ != i)
244 {
245 LOG(INFO,
246 "Id map entry does not point back to the expected index.");
247 return 24;
248 }
249 if (aliasMap.find(e->alias_) == aliasMap.end())
250 {
251 LOG(INFO, "Metadata alias is not in the alias map.");
252 return 25;
253 }
254 if (aliasMap.find(e->alias_)->idx_ != i)
255 {
256 LOG(INFO,
257 "Alis map entry does not point back to the expected index.");
258 return 26;
259 }
260 }
261 return 0;
262}
263
264#endif
265
267{
268 idMap.clear();
269 aliasMap.clear();
273 /* initialize the freeList */
274 for (size_t i = 0; i < entries; ++i)
275 {
277 pool[i].older_ = freeList;
278 freeList.idx_ = i;
279 }
280}
281
282void debug_print_entry(void *, NodeID id, NodeAlias alias)
283{
284 LOG(INFO, "[%012" PRIx64 "]: %03X", id, alias);
285}
286
287void debug_print_cache(AliasCache *c)
288{
289 LOG(INFO, "Alias cache:");
290 c->for_each(&debug_print_entry, nullptr);
291}
292
298{
299 HASSERT(id != 0);
300 HASSERT(alias != 0);
301
302 Metadata *insert;
303
304 auto it = aliasMap.find(alias);
305 if (alias == NOT_RESPONDING)
306 {
307 // We can have more than one NOT_RESPONDING entry.
308 it = aliasMap.end();
309 }
310 if (it != aliasMap.end())
311 {
312 /* we already have a mapping for this alias, so lets remove it */
313 insert = it->deref(this);
314 auto nid = insert->get_node_id();
315 remove(insert->alias_);
316
317 if (removeCallback)
318 {
319 /* tell the interface layer that we removed this mapping */
320 (*removeCallback)(nid, insert->alias_, context);
321 }
322 }
323 auto nit = idMap.find(id);
324 if (nit != idMap.end())
325 {
326 /* we already have a mapping for this id, so lets remove it */
327 insert = nit->deref(this);
328 auto nid = insert->get_node_id();
329 remove(insert->alias_);
330
331 if (removeCallback)
332 {
333 /* tell the interface layer that we removed this mapping */
334 (*removeCallback)(nid, insert->alias_, context);
335 }
336 }
337
338 if (!freeList.empty())
339 {
340 /* found an empty slot */
341 insert = freeList.deref(this);
342 freeList = insert->older_;
343 }
344 else
345 {
346 HASSERT(!oldest.empty() && !newest.empty());
347
348 /* kick out the oldest mapping and re-link the oldest endpoint */
349 insert = oldest.deref(this);
350 auto second = insert->newer_;
351 if (!second.empty())
352 {
353 second.deref(this)->older_.idx_ = NONE_ENTRY;
354 }
355 if (insert == newest.deref(this))
356 {
358 }
359 oldest = second;
360
362 idMap.erase(idMap.find(insert->get_node_id()));
363
364 if (removeCallback)
365 {
366 /* tell the interface layer that we removed this mapping */
367 (*removeCallback)(insert->get_node_id(), insert->alias_, context);
368 }
369 }
370
371 if (alias == NOT_RESPONDING)
372 {
373 // This code will make all NOT_RESPONDING aliases unique in our map.
374 unsigned ofs = insert - pool;
375 alias = NOT_RESPONDING | ofs;
376 auto it = aliasMap.find(alias);
377 HASSERT(it == aliasMap.end());
378 }
379 insert->set_node_id(id);
380 insert->alias_ = alias;
381
382 PoolIdx n;
383 n.idx_ = insert - pool;
385 idMap.insert(PoolIdx(n));
386
387 /* update the time based list */
388 insert->newer_.idx_ = NONE_ENTRY;
389 if (newest.empty())
390 {
391 /* if newest == NULL, then oldest must also be NULL */
393
394 insert->older_.idx_ = NONE_ENTRY;
395 oldest = n;
396 }
397 else
398 {
399 insert->older_ = newest;
400 newest.deref(this)->newer_ = n;
401 }
402
403 newest = n;
404
405#if defined(TEST_CONSISTENCY)
406 consistency_result = check_consistency();
407 HASSERT(0 == consistency_result);
408#endif
409}
410
417{
418 auto it = aliasMap.find(alias);
419
420 if (it != aliasMap.end())
421 {
422 Metadata *metadata = it->deref(this);
423 aliasMap.erase(it);
424 idMap.erase(idMap.find(metadata->get_node_id()));
425 // Ensures that the AME query handler does not find this metadata.
426 metadata->set_node_id(0);
427
428 // Removes metadata from the linked lists.
429 if (!metadata->newer_.empty())
430 {
431 metadata->newer_.deref(this)->older_ = metadata->older_;
432 }
433 if (!metadata->older_.empty())
434 {
435 metadata->older_.deref(this)->newer_ = metadata->newer_;
436 }
437 if (metadata == newest.deref(this))
438 {
439 newest = metadata->older_;
440 }
441 if (metadata == oldest.deref(this))
442 {
443 oldest = metadata->newer_;
444 }
445
446 // Adds metadata to the freelist.
447 metadata->older_ = freeList;
448 freeList.idx_ = metadata - pool;
449 }
450
451#if defined(TEST_CONSISTENCY)
452 consistency_result = check_consistency();
453 HASSERT(0 == consistency_result);
454#endif
455}
456
457bool AliasCache::retrieve(unsigned entry, NodeID* node, NodeAlias* alias)
458{
459 HASSERT(entry < size());
460 Metadata* md = pool + entry;
461 if (!md->alias_)
462 {
463 return false;
464 }
465 if (node)
466 {
467 *node = md->get_node_id();
468 }
469 if (alias)
470 {
471 *alias = resolve_notresponding(md->alias_);
472 }
473 return true;
474}
475
477{
478 auto it = idMap.upper_bound(bound);
479 if (it == idMap.end())
480 {
481 return false;
482 }
483 Metadata *metadata = it->deref(this);
484 if (alias)
485 {
486 *alias = resolve_notresponding(metadata->alias_);
487 }
488 if (node)
489 {
490 *node = metadata->get_node_id();
491 }
492 return true;
493}
494
500{
501 HASSERT(id != 0);
502
503 auto it = idMap.find(id);
504
505 if (it != idMap.end())
506 {
507 Metadata *metadata = it->deref(this);
508
509 /* update timestamp */
510 touch(metadata);
511 return resolve_notresponding(metadata->alias_);
512 }
513
514 /* no match found */
515 return 0;
516}
517
523{
524 if (alias == 0)
525 {
526 return 0;
527 }
528
529 auto it = aliasMap.find(alias);
530
531 if (it != aliasMap.end())
532 {
533 Metadata *metadata = it->deref(this);
534
535 /* update timestamp */
536 touch(metadata);
537 return metadata->get_node_id();
538 }
539
540 /* no match found */
541 return 0;
542}
543
549void AliasCache::for_each(void (*callback)(void*, NodeID, NodeAlias), void *context)
550{
551 HASSERT(callback != NULL);
552
553 for (PoolIdx idx = newest; !idx.empty(); idx = idx.deref(this)->older_)
554 {
555 Metadata *metadata = idx.deref(this);
556 (*callback)(context, metadata->get_node_id(),
557 resolve_notresponding(metadata->alias_));
558 }
559}
560
565{
566 NodeAlias alias;
567
568 do
569 {
570 /* calculate the alias given the current seed */
571 alias = (seed ^ (seed >> 12) ^ (seed >> 24) ^ (seed >> 36)) & 0xfff;
572
573 /* calculate the next seed */
574 seed = ((((1 << 9) + 1) * (seed) + CONSTANT)) & 0xffffffffffff;
575 } while (alias == 0 || lookup(alias) != 0);
576
577 /* new random alias */
578 return alias;
579}
580
585{
586 if (metadata != newest.deref(this))
587 {
588 if (metadata == oldest.deref(this))
589 {
590 oldest = metadata->newer_;
592 }
593 else
594 {
595 /* we have someone older */
596 metadata->older_.deref(this)->newer_ = metadata->newer_;
597 }
598 metadata->newer_.deref(this)->older_ = metadata->older_;
599 metadata->newer_.idx_ = NONE_ENTRY;
600 metadata->older_ = newest;
601 newest.deref(this)->newer_.idx_ = metadata - pool;
602 newest.idx_ = metadata - pool;
603 }
604#if defined(TEST_CONSISTENCY)
605 consistency_result = check_consistency();
606 HASSERT(0 == consistency_result);
607#endif
608}
609
610}
#define CONSTANT
constant for random number generation
iterator find(key_type key)
Searches for a single entry.
void erase(const iterator &it)
Removes an entry from the vector, pointed by an iterator.
iterator upper_bound(key_type key)
void insert(data_type &&d)
Adds new entry to the vector.
void clear()
Removes all entries.
Encapsulation of a pointer into the pool array.
Metadata * deref(AliasCache *parent)
Dereferences a pool index as if it was a pointer.
uint16_t idx_
Indexes the pool of the AliasCache.
PoolIdx freeList
list of unused mapping entries (index into pool)
bool next_entry(NodeID bound, NodeID *node, NodeAlias *alias)
Retrieves the next entry by increasing node ID.
Metadata * pool
pointer to allocated Metadata pool
NodeAlias generate()
Generate a 12-bit pseudo-random alias for a given alias cache.
void remove(NodeAlias alias)
Remove an alias from an alias cache.
AliasMap aliasMap
Map of alias to corresponding Metadata.
void touch(Metadata *metadata)
Update the time stamp for a given entry.
size_t entries
How many metadata entries have we allocated.
NodeAlias lookup(NodeID id)
Lookup a node's alias based on its Node ID.
void * context
context pointer to pass in with remove_callback
static constexpr uint16_t NONE_ENTRY
Sentinel entry for empty lists.
void for_each(void(*callback)(void *, NodeID, NodeAlias), void *context)
Call the given callback function once for each alias tracked.
PoolIdx newest
newest, most recently touched entry (index into pool)
IdMap idMap
Map of Node ID to corresponding Metadata.
void clear()
Reinitializes the entire map.
void(* removeCallback)(NodeID id, NodeAlias alias, void *)
callback function to be used when we remove an entry from the cache
PoolIdx oldest
oldest untouched entry (index into pool)
bool retrieve(unsigned entry, NodeID *node, NodeAlias *alias)
Retrieves an entry by index.
size_t size()
Returns the total number of aliases that can be cached.
NodeID seed
Seed for the generation of the next alias.
int check_consistency()
Visible for testing.
void add(NodeID id, NodeAlias alias)
Add an alias to an alias cache.
#define LOG(level, message...)
Conditionally write a message to the logging output.
Definition logging.h:99
static const int INFO
Loglevel that is printed by default, reporting some status information.
Definition logging.h:57
#define HASSERT(x)
Checks that the value of expression x is true, else terminates the current process.
Definition macros.h:138
static NodeAlias resolve_notresponding(NodeAlias stored)
This code removes the unique bits in the stored node alias in case this is a NOT_RESPONDING entry.
uint64_t NodeID
48-bit NMRAnet Node ID type
static const NodeAlias NOT_RESPONDING
Guard value put into the the internal node alias maps when a node ID could not be translated to a val...
uint16_t NodeAlias
Alias to a 48-bit NMRAnet Node ID type.
Interesting information about a given cache entry.
NodeAlias alias_
OpenLCB-CAN alias.
void set_node_id(NodeID id)
Sets the node ID field.
PoolIdx older_
Index of next-older entry according to the LRU linked list.
PoolIdx newer_
Index of next-newer entry according to the LRU linked list.