4 This file is part of avahi.
6 avahi is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as
8 published by the Free Software Foundation; either version 2.1 of the
9 License, or (at your option) any later version.
11 avahi is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
14 Public License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with avahi; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
30 #include <avahi-common/timeval.h>
31 #include <avahi-common/malloc.h>
37 #define AVAHI_CACHE_ENTRIES_MAX 500
39 static void remove_entry(AvahiCache *c, AvahiCacheEntry *e) {
45 /* avahi_log_debug("removing from cache: %p %p", c, e); */
47 /* Remove from hash table */
48 t = avahi_hashmap_lookup(c->hashmap, e->record->key);
49 AVAHI_LLIST_REMOVE(AvahiCacheEntry, by_key, t, e);
51 avahi_hashmap_replace(c->hashmap, t->record->key, t);
53 avahi_hashmap_remove(c->hashmap, e->record->key);
55 /* Remove from linked list */
56 AVAHI_LLIST_REMOVE(AvahiCacheEntry, entry, c->entries, e);
59 avahi_time_event_free(e->time_event);
61 avahi_multicast_lookup_engine_notify(c->server->multicast_lookup_engine, c->interface, e->record, AVAHI_BROWSER_REMOVE);
63 avahi_record_unref(e->record);
67 assert(c->n_entries-- >= 1);
70 AvahiCache *avahi_cache_new(AvahiServer *server, AvahiInterface *iface) {
74 if (!(c = avahi_new(AvahiCache, 1))) {
75 avahi_log_error(__FILE__": Out of memory.");
76 return NULL; /* OOM */
82 if (!(c->hashmap = avahi_hashmap_new((AvahiHashFunc) avahi_key_hash, (AvahiEqualFunc) avahi_key_equal, NULL, NULL))) {
83 avahi_log_error(__FILE__": Out of memory.");
85 return NULL; /* OOM */
88 AVAHI_LLIST_HEAD_INIT(AvahiCacheEntry, c->entries);
91 c->last_rand_timestamp = 0;
96 void avahi_cache_free(AvahiCache *c) {
100 remove_entry(c, c->entries);
101 assert(c->n_entries == 0);
103 avahi_hashmap_free(c->hashmap);
108 static AvahiCacheEntry *lookup_key(AvahiCache *c, AvahiKey *k) {
112 assert(!avahi_key_is_pattern(k));
114 return avahi_hashmap_lookup(c->hashmap, k);
117 void* avahi_cache_walk(AvahiCache *c, AvahiKey *pattern, AvahiCacheWalkCallback cb, void* userdata) {
124 if (avahi_key_is_pattern(pattern)) {
125 AvahiCacheEntry *e, *n;
127 for (e = c->entries; e; e = n) {
130 if (avahi_key_pattern_match(pattern, e->record->key))
131 if ((ret = cb(c, pattern, e, userdata)))
136 AvahiCacheEntry *e, *n;
138 for (e = lookup_key(c, pattern); e; e = n) {
141 if ((ret = cb(c, pattern, e, userdata)))
149 static void* lookup_record_callback(AvahiCache *c, AvahiKey *pattern, AvahiCacheEntry *e, void *userdata) {
154 if (avahi_record_equal_no_ttl(e->record, userdata))
160 static AvahiCacheEntry *lookup_record(AvahiCache *c, AvahiRecord *r) {
164 return avahi_cache_walk(c, r->key, lookup_record_callback, r);
167 static void next_expiry(AvahiCache *c, AvahiCacheEntry *e, unsigned percent);
169 static void elapse_func(AvahiTimeEvent *t, void *userdata) {
170 AvahiCacheEntry *e = userdata;
172 unsigned percent = 0;
177 /* txt = avahi_record_to_string(e->record); */
181 case AVAHI_CACHE_EXPIRY_FINAL:
182 case AVAHI_CACHE_POOF_FINAL:
183 case AVAHI_CACHE_GOODBYE_FINAL:
184 case AVAHI_CACHE_REPLACE_FINAL:
186 remove_entry(e->cache, e);
189 /* avahi_log_debug("Removing entry from cache due to expiration (%s)", txt); */
192 case AVAHI_CACHE_VALID:
193 case AVAHI_CACHE_POOF:
194 e->state = AVAHI_CACHE_EXPIRY1;
198 case AVAHI_CACHE_EXPIRY1:
199 e->state = AVAHI_CACHE_EXPIRY2;
202 case AVAHI_CACHE_EXPIRY2:
203 e->state = AVAHI_CACHE_EXPIRY3;
207 case AVAHI_CACHE_EXPIRY3:
208 e->state = AVAHI_CACHE_EXPIRY_FINAL;
217 /* Request a cache update if we are subscribed to this entry */
218 if (avahi_querier_shall_refresh_cache(e->cache->interface, e->record->key))
219 avahi_interface_post_query(e->cache->interface, e->record->key, 0, NULL);
221 /* Check again later */
222 next_expiry(e->cache, e, percent);
226 /* avahi_free(txt); */
229 static void update_time_event(AvahiCache *c, AvahiCacheEntry *e) {
234 avahi_time_event_update(e->time_event, &e->expiry);
236 e->time_event = avahi_time_event_new(c->server->time_event_queue, &e->expiry, elapse_func, e);
239 static void next_expiry(AvahiCache *c, AvahiCacheEntry *e, unsigned percent) {
240 AvahiUsec usec, left, right;
245 assert(percent > 0 && percent <= 100);
247 usec = (AvahiUsec) e->record->ttl * 10000;
249 left = usec * percent;
250 right = usec * (percent+2); /* 2% jitter */
254 if (now >= c->last_rand_timestamp + 10) {
255 c->last_rand = rand();
256 c->last_rand_timestamp = now;
259 usec = left + (AvahiUsec) ((double) (right-left) * c->last_rand / (RAND_MAX+1.0));
261 e->expiry = e->timestamp;
262 avahi_timeval_add(&e->expiry, usec);
264 /* g_message("wake up in +%lu seconds", e->expiry.tv_sec - e->timestamp.tv_sec); */
266 update_time_event(c, e);
269 static void expire_in_one_second(AvahiCache *c, AvahiCacheEntry *e, AvahiCacheEntryState state) {
274 gettimeofday(&e->expiry, NULL);
275 avahi_timeval_add(&e->expiry, 1000000); /* 1s */
276 update_time_event(c, e);
279 void avahi_cache_update(AvahiCache *c, AvahiRecord *r, int cache_flush, const AvahiAddress *a) {
283 assert(r && r->ref >= 1);
285 /* txt = avahi_record_to_string(r); */
288 /* This is a goodbye request */
292 if ((e = lookup_record(c, r)))
293 expire_in_one_second(c, e, AVAHI_CACHE_GOODBYE_FINAL);
296 AvahiCacheEntry *e = NULL, *first;
299 gettimeofday(&now, NULL);
301 /* This is an update request */
303 if ((first = lookup_key(c, r->key))) {
307 /* For unique entries drop all entries older than one second */
308 for (e = first; e; e = e->by_key_next) {
311 t = avahi_timeval_diff(&now, &e->timestamp);
314 expire_in_one_second(c, e, AVAHI_CACHE_REPLACE_FINAL);
318 /* Look for exactly the same entry */
319 for (e = first; e; e = e->by_key_next)
320 if (avahi_record_equal_no_ttl(e->record, r))
326 /* avahi_log_debug("found matching cache entry"); */
328 /* We need to update the hash table key if we replace the
330 if (e->by_key_prev == NULL)
331 avahi_hashmap_replace(c->hashmap, r->key, e);
333 /* Update the record */
334 avahi_record_unref(e->record);
335 e->record = avahi_record_ref(r);
337 /* avahi_log_debug("cache: updating %s", txt); */
340 /* No entry found, therefore we create a new one */
342 /* avahi_log_debug("cache: couldn't find matching cache entry for %s", txt); */
344 if (c->n_entries >= AVAHI_CACHE_ENTRIES_MAX)
347 if (!(e = avahi_new(AvahiCacheEntry, 1))) {
348 avahi_log_error(__FILE__": Out of memory");
353 e->time_event = NULL;
354 e->record = avahi_record_ref(r);
356 /* Append to hash table */
357 AVAHI_LLIST_PREPEND(AvahiCacheEntry, by_key, first, e);
358 avahi_hashmap_replace(c->hashmap, e->record->key, first);
360 /* Append to linked list */
361 AVAHI_LLIST_PREPEND(AvahiCacheEntry, entry, c->entries, e);
365 /* Notify subscribers */
366 avahi_multicast_lookup_engine_notify(c->server->multicast_lookup_engine, c->interface, e->record, AVAHI_BROWSER_NEW);
371 next_expiry(c, e, 80);
372 e->state = AVAHI_CACHE_VALID;
373 e->cache_flush = cache_flush;
376 /* avahi_free(txt); */
380 AvahiDumpCallback callback;
384 static void dump_callback(void* key, void* data, void* userdata) {
385 AvahiCacheEntry *e = data;
387 struct dump_data *dump_data = userdata;
393 for (; e; e = e->by_key_next) {
396 if (!(t = avahi_record_to_string(e->record)))
399 dump_data->callback(t, dump_data->userdata);
404 int avahi_cache_dump(AvahiCache *c, AvahiDumpCallback callback, void* userdata) {
405 struct dump_data data;
410 callback(";;; CACHE DUMP FOLLOWS ;;;", userdata);
412 data.callback = callback;
413 data.userdata = userdata;
415 avahi_hashmap_foreach(c->hashmap, dump_callback, &data);
420 int avahi_cache_entry_half_ttl(AvahiCache *c, AvahiCacheEntry *e) {
427 gettimeofday(&now, NULL);
429 age = (unsigned) (avahi_timeval_diff(&now, &e->timestamp)/1000000);
431 /* avahi_log_debug("age: %lli, ttl/2: %u", age, e->record->ttl); */
433 return age >= e->record->ttl/2;
436 void avahi_cache_flush(AvahiCache *c) {
440 remove_entry(c, c->entries);
443 /*** Passive observation of failure ***/
445 static void* start_poof_callback(AvahiCache *c, AvahiKey *pattern, AvahiCacheEntry *e, void *userdata) {
446 AvahiAddress *a = userdata;
454 gettimeofday(&now, NULL);
457 case AVAHI_CACHE_VALID:
459 /* The entry was perfectly valid till, now, so let's enter
462 e->state = AVAHI_CACHE_POOF;
463 e->poof_address = *a;
464 e->poof_timestamp = now;
469 case AVAHI_CACHE_POOF:
470 if (avahi_timeval_diff(&now, &e->poof_timestamp) < 1000000)
473 e->poof_timestamp = now;
474 e->poof_address = *a;
477 /* This is the 4th time we got no response, so let's
478 * fucking remove this entry. */
480 expire_in_one_second(c, e, AVAHI_CACHE_POOF_FINAL);
490 void avahi_cache_start_poof(AvahiCache *c, AvahiKey *key, const AvahiAddress *a) {
494 avahi_cache_walk(c, key, start_poof_callback, (void*) a);
497 void avahi_cache_stop_poof(AvahiCache *c, AvahiRecord *record, const AvahiAddress *a) {
504 if (!(e = lookup_record(c, record)))
507 /* This function is called for each response suppression
508 record. If the matching cache entry is in POOF state and the
509 query address is the same, we put it back into valid mode */
511 if (e->state == AVAHI_CACHE_POOF || e->state == AVAHI_CACHE_POOF_FINAL)
512 if (avahi_address_cmp(a, &e->poof_address) == 0) {
513 e->state = AVAHI_CACHE_VALID;
514 next_expiry(c, e, 80);