]> git.meshlink.io Git - catta/blob - libavahi-core/cache.c
move the sources to libavahi-core/
[catta] / libavahi-core / cache.c
1 #include <string.h>
2
3 #include "util.h"
4 #include "cache.h"
5
6 static void remove_entry(AvahiCache *c, AvahiCacheEntry *e) {
7     AvahiCacheEntry *t;
8
9     g_assert(c);
10     g_assert(e);
11
12 /*     g_message("removing from cache: %p %p", c, e); */
13
14     /* Remove from hash table */
15     t = g_hash_table_lookup(c->hash_table, e->record->key);
16     AVAHI_LLIST_REMOVE(AvahiCacheEntry, by_key, t, e);
17     if (t)
18         g_hash_table_replace(c->hash_table, t->record->key, t);
19     else
20         g_hash_table_remove(c->hash_table, e->record->key);
21
22     /* Remove from linked list */
23     AVAHI_LLIST_REMOVE(AvahiCacheEntry, entry, c->entries, e);
24         
25     if (e->time_event)
26         avahi_time_event_queue_remove(c->server->time_event_queue, e->time_event);
27
28     avahi_subscription_notify(c->server, c->interface, e->record, AVAHI_SUBSCRIPTION_REMOVE);
29
30     avahi_record_unref(e->record);
31     
32     g_free(e);
33 }
34
35 AvahiCache *avahi_cache_new(AvahiServer *server, AvahiInterface *iface) {
36     AvahiCache *c;
37     g_assert(server);
38
39     c = g_new(AvahiCache, 1);
40     c->server = server;
41     c->interface = iface;
42     c->hash_table = g_hash_table_new((GHashFunc) avahi_key_hash, (GEqualFunc) avahi_key_equal);
43
44     AVAHI_LLIST_HEAD_INIT(AvahiCacheEntry, c->entries);
45     
46     return c;
47 }
48
49 void avahi_cache_free(AvahiCache *c) {
50     g_assert(c);
51
52     while (c->entries)
53         remove_entry(c, c->entries);
54     
55     g_hash_table_destroy(c->hash_table);
56     
57     g_free(c);
58 }
59
60 AvahiCacheEntry *avahi_cache_lookup_key(AvahiCache *c, AvahiKey *k) {
61     g_assert(c);
62     g_assert(k);
63
64     g_assert(!avahi_key_is_pattern(k));
65     
66     return g_hash_table_lookup(c->hash_table, k);
67 }
68
69 gpointer avahi_cache_walk(AvahiCache *c, AvahiKey *pattern, AvahiCacheWalkCallback cb, gpointer userdata) {
70     gpointer ret;
71     
72     g_assert(c);
73     g_assert(pattern);
74     g_assert(cb);
75     
76     if (avahi_key_is_pattern(pattern)) {
77         AvahiCacheEntry *e, *n;
78         
79         for (e = c->entries; e; e = n) {
80             n = e->entry_next;
81             
82             if (avahi_key_pattern_match(pattern, e->record->key))
83                 if ((ret = cb(c, pattern, e, userdata)))
84                     return ret;
85         }
86         
87     } else {
88         AvahiCacheEntry *e, *n;
89
90         for (e = avahi_cache_lookup_key(c, pattern); e; e = n) {
91             n = e->by_key_next;
92                 
93             if ((ret = cb(c, pattern, e, userdata)))
94                 return ret;
95         }
96     }
97
98     return NULL;
99 }
100
101 static gpointer lookup_record_callback(AvahiCache *c, AvahiKey *pattern, AvahiCacheEntry *e, void *userdata) {
102     g_assert(c);
103     g_assert(pattern);
104     g_assert(e);
105
106     if (avahi_record_equal_no_ttl(e->record, userdata))
107         return e;
108
109     return NULL;
110 }
111
112 AvahiCacheEntry *avahi_cache_lookup_record(AvahiCache *c, AvahiRecord *r) {
113     AvahiCacheEntry *e;
114     
115     g_assert(c);
116     g_assert(r);
117
118     return avahi_cache_walk(c, r->key, lookup_record_callback, r);
119 }
120
121 static void next_expiry(AvahiCache *c, AvahiCacheEntry *e, guint percent);
122
123 static void elapse_func(AvahiTimeEvent *t, void *userdata) {
124     AvahiCacheEntry *e = userdata;
125     
126     g_assert(t);
127     g_assert(e);
128
129     if (e->state == AVAHI_CACHE_FINAL) {
130         remove_entry(e->cache, e);
131         g_message("Removing entry from cache due to expiration");
132     } else {
133         guint percent = 0;
134     
135         switch (e->state) {
136             case AVAHI_CACHE_VALID:
137                 e->state = AVAHI_CACHE_EXPIRY1;
138                 percent = 85;
139                 break;
140                 
141             case AVAHI_CACHE_EXPIRY1:
142                 e->state = AVAHI_CACHE_EXPIRY2;
143                 percent = 90;
144                 break;
145             case AVAHI_CACHE_EXPIRY2:
146                 e->state = AVAHI_CACHE_EXPIRY3;
147                 percent = 95;
148                 break;
149                 
150             case AVAHI_CACHE_EXPIRY3:
151                 e->state = AVAHI_CACHE_FINAL;
152                 percent = 100;
153                 break;
154
155             default:
156                 ;
157         }
158
159         g_assert(percent > 0);
160
161         g_message("Requesting cache entry update at %i%%.", percent);
162
163         /* Request a cache update, if we are subscribed to this entry */
164         if (avahi_is_subscribed(e->cache->server, e->record->key))
165             avahi_interface_post_query(e->cache->interface, e->record->key, TRUE);
166
167         /* Check again later */
168         next_expiry(e->cache, e, percent);
169     }
170 }
171
172 static void update_time_event(AvahiCache *c, AvahiCacheEntry *e) {
173     g_assert(c);
174     g_assert(e);
175     
176     if (e->time_event)
177         avahi_time_event_queue_update(c->server->time_event_queue, e->time_event, &e->expiry);
178     else
179         e->time_event = avahi_time_event_queue_add(c->server->time_event_queue, &e->expiry, elapse_func, e);
180 }
181
182 static void next_expiry(AvahiCache *c, AvahiCacheEntry *e, guint percent) {
183     gulong usec;
184
185     g_assert(c);
186     g_assert(e);
187     g_assert(percent > 0 && percent <= 100);
188
189     e->expiry = e->timestamp;
190
191     usec = e->record->ttl * 10000;
192
193     /* 2% jitter */
194     usec = g_random_int_range(usec*percent, usec*(percent+2));
195     
196     g_time_val_add(&e->expiry, usec);
197     update_time_event(c, e);
198 }
199
200 void avahi_cache_update(AvahiCache *c, AvahiRecord *r, gboolean unique, const AvahiAddress *a) {
201     AvahiCacheEntry *e, *t;
202     gchar *txt;
203     
204     g_assert(c);
205     g_assert(r && r->ref >= 1);
206
207     g_message("cache update: %s", (txt = avahi_record_to_string(r)));
208     g_free(txt);
209
210     if (r->ttl == 0) {
211
212         /* This is a goodbye request */
213
214         if ((e = avahi_cache_lookup_record(c, r))) {
215
216             e->state = AVAHI_CACHE_FINAL;
217             g_get_current_time(&e->timestamp);
218             e->expiry = e->timestamp;
219             g_time_val_add(&e->expiry, 1000000); /* 1s */
220             update_time_event(c, e);
221         }
222
223     } else {
224
225         /* This is an update request */
226
227         if ((t = e = avahi_cache_lookup_key(c, r->key))) {
228         
229             if (unique) {
230                 
231                 /* For unique records, remove all entries but one */
232                 while (e->by_key_next)
233                     remove_entry(c, e->by_key_next);
234                 
235             } else {
236                 
237                 /* For non-unique record, look for exactly the same entry */
238                 for (; e; e = e->by_key_next)
239                     if (avahi_record_equal_no_ttl(e->record, r))
240                         break;
241             }
242         }
243     
244         if (e) {
245             
246 /*         g_message("found matching cache entry"); */
247             
248             /* We are the first in the linked list so let's replace the hash table key with the new one */
249             if (e->by_key_prev == NULL)
250                 g_hash_table_replace(c->hash_table, r->key, e);
251             
252             /* Notify subscribers */
253             if (!avahi_record_equal_no_ttl(e->record, r))
254                 avahi_subscription_notify(c->server, c->interface, r, AVAHI_SUBSCRIPTION_CHANGE);    
255             
256             /* Update the record */
257             avahi_record_unref(e->record);
258             e->record = avahi_record_ref(r);
259             
260         } else {
261             /* No entry found, therefore we create a new one */
262             
263 /*         g_message("couldn't find matching cache entry"); */
264             
265             e = g_new(AvahiCacheEntry, 1);
266             e->cache = c;
267             e->time_event = NULL;
268             e->record = avahi_record_ref(r);
269
270             /* Append to hash table */
271             AVAHI_LLIST_PREPEND(AvahiCacheEntry, by_key, t, e);
272             g_hash_table_replace(c->hash_table, e->record->key, t);
273
274             /* Append to linked list */
275             AVAHI_LLIST_PREPEND(AvahiCacheEntry, entry, c->entries, e);
276
277             /* Notify subscribers */
278             avahi_subscription_notify(c->server, c->interface, e->record, AVAHI_SUBSCRIPTION_NEW);
279         } 
280         
281         e->origin = *a;
282         g_get_current_time(&e->timestamp);
283         next_expiry(c, e, 80);
284         e->state = AVAHI_CACHE_VALID;
285     }
286 }
287
288 static gpointer drop_key_callback(AvahiCache *c, AvahiKey *pattern, AvahiCacheEntry *e, gpointer userdata) {
289     g_assert(c);
290     g_assert(pattern);
291     g_assert(e);
292
293     remove_entry(c, e);
294     return NULL;
295 }
296
297 void avahi_cache_drop_key(AvahiCache *c, AvahiKey *k) {
298     g_assert(c);
299     g_assert(k);
300
301     avahi_cache_walk(c, k, drop_key_callback, NULL);
302 }
303
304 void avahi_cache_drop_record(AvahiCache *c, AvahiRecord *r) {
305     AvahiCacheEntry *e;
306     
307     g_assert(c);
308     g_assert(r);
309
310     if ((e = avahi_cache_lookup_record(c, r))) 
311         remove_entry(c, e);
312 }
313
314 static void func(gpointer key, gpointer data, gpointer userdata) {
315     AvahiCacheEntry *e = data;
316     AvahiKey *k = key;
317     gchar *t;
318
319     t = avahi_record_to_string(e->record);
320     fprintf((FILE*) userdata, "%s\n", t);
321     g_free(t);
322 }
323
324 void avahi_cache_dump(AvahiCache *c, FILE *f) {
325     g_assert(c);
326     g_assert(f);
327
328     fprintf(f, ";;; CACHE DUMP FOLLOWS ;;;\n");
329     g_hash_table_foreach(c->hash_table, func, f);
330 }
331
332 gboolean avahi_cache_entry_half_ttl(AvahiCache *c, AvahiCacheEntry *e) {
333     GTimeVal now;
334     guint age;
335     
336     g_assert(c);
337     g_assert(e);
338
339     g_get_current_time(&now);
340
341     age = avahi_timeval_diff(&now, &e->timestamp)/1000000;
342
343     g_message("age: %u, ttl/2: %u", age, e->record->ttl);
344     
345     return age >= e->record->ttl/2;
346 }