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
32 #define AVAHI_MAX_CACHE_ENTRIES 200
34 static void remove_entry(AvahiCache *c, AvahiCacheEntry *e) {
40 /* avahi_log_debug("removing from cache: %p %p", c, e); */
42 /* Remove from hash table */
43 t = g_hash_table_lookup(c->hash_table, e->record->key);
44 AVAHI_LLIST_REMOVE(AvahiCacheEntry, by_key, t, e);
46 g_hash_table_replace(c->hash_table, t->record->key, t);
48 g_hash_table_remove(c->hash_table, e->record->key);
50 /* Remove from linked list */
51 AVAHI_LLIST_REMOVE(AvahiCacheEntry, entry, c->entries, e);
54 avahi_time_event_queue_remove(c->server->time_event_queue, e->time_event);
56 avahi_browser_notify(c->server, c->interface, e->record, AVAHI_BROWSER_REMOVE);
58 avahi_record_unref(e->record);
62 g_assert(c->n_entries-- >= 1);
65 AvahiCache *avahi_cache_new(AvahiServer *server, AvahiInterface *iface) {
69 c = g_new(AvahiCache, 1);
72 c->hash_table = g_hash_table_new((GHashFunc) avahi_key_hash, (GEqualFunc) avahi_key_equal);
74 AVAHI_LLIST_HEAD_INIT(AvahiCacheEntry, c->entries);
80 void avahi_cache_free(AvahiCache *c) {
84 remove_entry(c, c->entries);
85 g_assert(c->n_entries == 0);
87 g_hash_table_destroy(c->hash_table);
92 AvahiCacheEntry *avahi_cache_lookup_key(AvahiCache *c, AvahiKey *k) {
96 g_assert(!avahi_key_is_pattern(k));
98 return g_hash_table_lookup(c->hash_table, k);
101 gpointer avahi_cache_walk(AvahiCache *c, AvahiKey *pattern, AvahiCacheWalkCallback cb, gpointer userdata) {
108 if (avahi_key_is_pattern(pattern)) {
109 AvahiCacheEntry *e, *n;
111 for (e = c->entries; e; e = n) {
114 if (avahi_key_pattern_match(pattern, e->record->key))
115 if ((ret = cb(c, pattern, e, userdata)))
120 AvahiCacheEntry *e, *n;
122 for (e = avahi_cache_lookup_key(c, pattern); e; e = n) {
125 if ((ret = cb(c, pattern, e, userdata)))
133 static gpointer lookup_record_callback(AvahiCache *c, AvahiKey *pattern, AvahiCacheEntry *e, void *userdata) {
138 if (avahi_record_equal_no_ttl(e->record, userdata))
144 AvahiCacheEntry *avahi_cache_lookup_record(AvahiCache *c, AvahiRecord *r) {
148 return avahi_cache_walk(c, r->key, lookup_record_callback, r);
151 static void next_expiry(AvahiCache *c, AvahiCacheEntry *e, guint percent);
153 static void elapse_func(AvahiTimeEvent *t, void *userdata) {
154 AvahiCacheEntry *e = userdata;
160 /* txt = avahi_record_to_string(e->record); */
162 if (e->state == AVAHI_CACHE_FINAL) {
163 remove_entry(e->cache, e);
164 /* avahi_log_debug("Removing entry from cache due to expiration (%s)", txt); */
169 case AVAHI_CACHE_VALID:
170 e->state = AVAHI_CACHE_EXPIRY1;
174 case AVAHI_CACHE_EXPIRY1:
175 e->state = AVAHI_CACHE_EXPIRY2;
178 case AVAHI_CACHE_EXPIRY2:
179 e->state = AVAHI_CACHE_EXPIRY3;
183 case AVAHI_CACHE_EXPIRY3:
184 e->state = AVAHI_CACHE_FINAL;
192 g_assert(percent > 0);
194 /* Request a cache update, if we are subscribed to this entry */
195 if (avahi_is_subscribed(e->cache->server, e->cache->interface, e->record->key)) {
196 /* avahi_log_debug("Requesting cache entry update at %i%% for %s.", percent, txt); */
197 avahi_interface_post_query(e->cache->interface, e->record->key, TRUE);
200 /* Check again later */
201 next_expiry(e->cache, e, percent);
207 static void update_time_event(AvahiCache *c, AvahiCacheEntry *e) {
212 avahi_time_event_queue_update(c->server->time_event_queue, e->time_event, &e->expiry);
214 e->time_event = avahi_time_event_queue_add(c->server->time_event_queue, &e->expiry, elapse_func, e);
217 static void next_expiry(AvahiCache *c, AvahiCacheEntry *e, guint percent) {
220 g_assert(percent > 0 && percent <= 100);
224 usec = ((AvahiUsec) e->record->ttl) * 10000;
227 usec = (AvahiUsec) g_random_double_range(usec*percent, usec*(percent+2));
228 /* g_message("next expiry: %lli (%s)", usec / 1000000, txt = avahi_record_to_string(e->record)); */
231 e->expiry = e->timestamp;
232 avahi_timeval_add(&e->expiry, usec);
234 /* g_message("wake up in +%lu seconds", e->expiry.tv_sec - e->timestamp.tv_sec); */
236 update_time_event(c, e);
239 static void expire_in_one_second(AvahiCache *c, AvahiCacheEntry *e) {
243 e->state = AVAHI_CACHE_FINAL;
244 g_get_current_time(&e->expiry);
245 avahi_timeval_add(&e->expiry, 1000000); /* 1s */
246 update_time_event(c, e);
249 void avahi_cache_update(AvahiCache *c, AvahiRecord *r, gboolean cache_flush, const AvahiAddress *a) {
253 g_assert(r && r->ref >= 1);
255 /* txt = avahi_record_to_string(r); */
258 /* This is a goodbye request */
262 if ((e = avahi_cache_lookup_record(c, r)))
263 expire_in_one_second(c, e);
266 AvahiCacheEntry *e = NULL, *first;
269 g_get_current_time(&now);
271 /* This is an update request */
273 if ((first = avahi_cache_lookup_key(c, r->key))) {
277 /* For unique entries drop all entries older than one second */
278 for (e = first; e; e = e->by_key_next) {
281 t = avahi_timeval_diff(&now, &e->timestamp);
284 expire_in_one_second(c, e);
288 /* Look for exactly the same entry */
289 for (e = first; e; e = e->by_key_next)
290 if (avahi_record_equal_no_ttl(e->record, r))
296 /* avahi_log_debug("found matching cache entry"); */
298 /* We need to update the hash table key if we replace the
300 if (e->by_key_prev == NULL)
301 g_hash_table_replace(c->hash_table, r->key, e);
303 /* Update the record */
304 avahi_record_unref(e->record);
305 e->record = avahi_record_ref(r);
307 /* avahi_log_debug("cache: updating %s", txt); */
310 /* No entry found, therefore we create a new one */
312 /* avahi_log_debug("cache: couldn't find matching cache entry for %s", txt); */
314 if (c->n_entries >= AVAHI_MAX_CACHE_ENTRIES)
319 e = g_new(AvahiCacheEntry, 1);
321 e->time_event = NULL;
322 e->record = avahi_record_ref(r);
324 /* Append to hash table */
325 AVAHI_LLIST_PREPEND(AvahiCacheEntry, by_key, first, e);
326 g_hash_table_replace(c->hash_table, e->record->key, first);
328 /* Append to linked list */
329 AVAHI_LLIST_PREPEND(AvahiCacheEntry, entry, c->entries, e);
331 /* Notify subscribers */
332 avahi_browser_notify(c->server, c->interface, e->record, AVAHI_BROWSER_NEW);
337 next_expiry(c, e, 80);
338 e->state = AVAHI_CACHE_VALID;
339 e->cache_flush = cache_flush;
345 static void dump_callback(gpointer key, gpointer data, gpointer userdata) {
346 AvahiCacheEntry *e = data;
352 for (; e; e = e->by_key_next) {
353 gchar *t = avahi_record_to_string(e->record);
354 fprintf((FILE*) userdata, "%s\n", t);
359 void avahi_cache_dump(AvahiCache *c, FILE *f) {
363 fprintf(f, ";;; CACHE DUMP FOLLOWS ;;;\n");
364 g_hash_table_foreach(c->hash_table, dump_callback, f);
367 gboolean avahi_cache_entry_half_ttl(AvahiCache *c, AvahiCacheEntry *e) {
374 g_get_current_time(&now);
376 age = avahi_timeval_diff(&now, &e->timestamp)/1000000;
378 /* avahi_log_debug("age: %lli, ttl/2: %u", age, e->record->ttl); */
380 return age >= e->record->ttl/2;
383 void avahi_cache_flush(AvahiCache *c) {
387 remove_entry(c, c->entries);