]> git.meshlink.io Git - catta/blob - avahi-core/query-sched.c
Add support for server state change callbacks
[catta] / avahi-core / query-sched.c
1 /* $Id$ */
2
3 /***
4   This file is part of avahi.
5  
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.
10  
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.
15  
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
19   USA.
20 ***/
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include "query-sched.h"
27 #include "util.h"
28
29 #define AVAHI_QUERY_HISTORY_MSEC 100
30 #define AVAHI_QUERY_DEFER_MSEC 100
31
32 typedef struct AvahiQueryJob AvahiQueryJob;
33 typedef struct AvahiKnownAnswer AvahiKnownAnswer;
34
35 struct AvahiQueryJob {
36     AvahiQueryScheduler *scheduler;
37     AvahiTimeEvent *time_event;
38     
39     gboolean done;
40     GTimeVal delivery;
41
42     AvahiKey *key;
43
44     AVAHI_LLIST_FIELDS(AvahiQueryJob, jobs);
45 };
46
47 struct AvahiKnownAnswer {
48     AvahiQueryScheduler *scheduler;
49     AvahiRecord *record;
50
51     AVAHI_LLIST_FIELDS(AvahiKnownAnswer, known_answer);
52 };
53
54 struct AvahiQueryScheduler {
55     AvahiInterface *interface;
56     AvahiTimeEventQueue *time_event_queue;
57
58     AVAHI_LLIST_HEAD(AvahiQueryJob, jobs);
59     AVAHI_LLIST_HEAD(AvahiQueryJob, history);
60     AVAHI_LLIST_HEAD(AvahiKnownAnswer, known_answers);
61 };
62
63 static AvahiQueryJob* job_new(AvahiQueryScheduler *s, AvahiKey *key, gboolean done) {
64     AvahiQueryJob *qj;
65     
66     g_assert(s);
67     g_assert(key);
68
69     qj = g_new(AvahiQueryJob, 1);
70     qj->scheduler = s;
71     qj->key = avahi_key_ref(key);
72     qj->time_event = NULL;
73     
74     if ((qj->done = done)) 
75         AVAHI_LLIST_PREPEND(AvahiQueryJob, jobs, s->history, qj);
76     else
77         AVAHI_LLIST_PREPEND(AvahiQueryJob, jobs, s->jobs, qj);
78
79     return qj;
80 }
81
82 static void job_free(AvahiQueryScheduler *s, AvahiQueryJob *qj) {
83     g_assert(s);
84     g_assert(qj);
85
86     if (qj->time_event)
87         avahi_time_event_queue_remove(s->time_event_queue, qj->time_event);
88
89     if (qj->done)
90         AVAHI_LLIST_REMOVE(AvahiQueryJob, jobs, s->history, qj);
91     else
92         AVAHI_LLIST_REMOVE(AvahiQueryJob, jobs, s->jobs, qj);
93
94     avahi_key_unref(qj->key);
95     g_free(qj);
96 }
97
98 static void elapse_callback(AvahiTimeEvent *e, gpointer data);
99
100 static void job_set_elapse_time(AvahiQueryScheduler *s, AvahiQueryJob *qj, guint msec, guint jitter) {
101     GTimeVal tv;
102
103     g_assert(s);
104     g_assert(qj);
105
106     avahi_elapse_time(&tv, msec, jitter);
107
108     if (qj->time_event)
109         avahi_time_event_queue_update(s->time_event_queue, qj->time_event, &tv);
110     else
111         qj->time_event = avahi_time_event_queue_add(s->time_event_queue, &tv, elapse_callback, qj);
112 }
113
114 static void job_mark_done(AvahiQueryScheduler *s, AvahiQueryJob *qj) {
115     g_assert(s);
116     g_assert(qj);
117
118     g_assert(!qj->done);
119
120     AVAHI_LLIST_REMOVE(AvahiQueryJob, jobs, s->jobs, qj);
121     AVAHI_LLIST_PREPEND(AvahiQueryJob, jobs, s->history, qj);
122
123     qj->done = TRUE;
124
125     job_set_elapse_time(s, qj, AVAHI_QUERY_HISTORY_MSEC, 0);
126     g_get_current_time(&qj->delivery);
127 }
128
129 AvahiQueryScheduler *avahi_query_scheduler_new(AvahiInterface *i) {
130     AvahiQueryScheduler *s;
131     g_assert(i);
132
133     s = g_new(AvahiQueryScheduler, 1);
134     s->interface = i;
135     s->time_event_queue = i->monitor->server->time_event_queue;
136     
137     AVAHI_LLIST_HEAD_INIT(AvahiQueryJob, s->jobs);
138     AVAHI_LLIST_HEAD_INIT(AvahiQueryJob, s->history);
139     AVAHI_LLIST_HEAD_INIT(AvahiKnownAnswer, s->known_answers);
140
141     return s;
142 }
143
144 void avahi_query_scheduler_free(AvahiQueryScheduler *s) {
145     g_assert(s);
146
147     g_assert(!s->known_answers);
148     avahi_query_scheduler_clear(s);
149     g_free(s);
150 }
151
152 void avahi_query_scheduler_clear(AvahiQueryScheduler *s) {
153     g_assert(s);
154     
155     while (s->jobs)
156         job_free(s, s->jobs);
157     while (s->history)
158         job_free(s, s->history);
159 }
160
161 static gpointer known_answer_walk_callback(AvahiCache *c, AvahiKey *pattern, AvahiCacheEntry *e, gpointer userdata) {
162     AvahiQueryScheduler *s = userdata;
163     AvahiKnownAnswer *ka;
164     
165     g_assert(c);
166     g_assert(pattern);
167     g_assert(e);
168     g_assert(s);
169
170     if (avahi_cache_entry_half_ttl(c, e))
171         return NULL;
172     
173     ka = g_new0(AvahiKnownAnswer, 1);
174     ka->scheduler = s;
175     ka->record = avahi_record_ref(e->record);
176
177     AVAHI_LLIST_PREPEND(AvahiKnownAnswer, known_answer, s->known_answers, ka);
178     return NULL;
179 }
180
181 static gboolean packet_add_query_job(AvahiQueryScheduler *s, AvahiDnsPacket *p, AvahiQueryJob *qj) {
182     g_assert(s);
183     g_assert(p);
184     g_assert(qj);
185
186     if (!avahi_dns_packet_append_key(p, qj->key, FALSE))
187         return FALSE;
188
189     /* Add all matching known answers to the list */
190     avahi_cache_walk(s->interface->cache, qj->key, known_answer_walk_callback, s);
191     
192     job_mark_done(s, qj);
193
194     return TRUE;
195 }
196
197 static void append_known_answers_and_send(AvahiQueryScheduler *s, AvahiDnsPacket *p) {
198     AvahiKnownAnswer *ka;
199     guint n;
200     g_assert(s);
201     g_assert(p);
202
203     n = 0;
204     
205     while ((ka = s->known_answers)) {
206         gboolean too_large = FALSE;
207
208         while (!avahi_dns_packet_append_record(p, ka->record, FALSE, 0)) {
209
210             if (avahi_dns_packet_is_empty(p)) {
211                 /* The record is too large to fit into one packet, so
212                    there's no point in sending it. Better is letting
213                    the owner of the record send it as a response. This
214                    has the advantage of a cache refresh. */
215
216                 too_large = TRUE;
217                 break;
218             }
219
220             avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_FLAGS, avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_FLAGS) | AVAHI_DNS_FLAG_TC);
221             avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_ANCOUNT, n);
222             avahi_interface_send_packet(s->interface, p);
223             avahi_dns_packet_free(p);
224
225             p = avahi_dns_packet_new_query(s->interface->hardware->mtu);
226             n = 0;
227         }
228
229         AVAHI_LLIST_REMOVE(AvahiKnownAnswer, known_answer, s->known_answers, ka);
230         avahi_record_unref(ka->record);
231         g_free(ka);
232
233         if (!too_large)
234             n++;
235     }
236     
237     avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_ANCOUNT, n);
238     avahi_interface_send_packet(s->interface, p);
239     avahi_dns_packet_free(p);
240 }
241
242 static void elapse_callback(AvahiTimeEvent *e, gpointer data) {
243     AvahiQueryJob *qj = data;
244     AvahiQueryScheduler *s;
245     AvahiDnsPacket *p;
246     guint n;
247     gboolean b;
248
249     g_assert(qj);
250     s = qj->scheduler;
251
252     if (qj->done) {
253         /* Lets remove it  from the history */
254         job_free(s, qj);
255         return;
256     }
257
258     g_assert(!s->known_answers);
259     
260     p = avahi_dns_packet_new_query(s->interface->hardware->mtu);
261     b = packet_add_query_job(s, p, qj);
262     g_assert(b); /* An query must always fit in */
263     n = 1;
264
265     /* Try to fill up packet with more queries, if available */
266     while (s->jobs) {
267
268         if (!packet_add_query_job(s, p, s->jobs))
269             break;
270
271         n++;
272     }
273
274     avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_QDCOUNT, n);
275
276     /* Now add known answers */
277     append_known_answers_and_send(s, p);
278 }
279
280 static AvahiQueryJob* find_scheduled_job(AvahiQueryScheduler *s, AvahiKey *key) {
281     AvahiQueryJob *qj;
282
283     g_assert(s);
284     g_assert(key);
285
286     for (qj = s->jobs; qj; qj = qj->jobs_next) {
287         g_assert(!qj->done);
288         
289         if (avahi_key_equal(qj->key, key))
290             return qj;
291     }
292
293     return NULL;
294 }
295
296 static AvahiQueryJob* find_history_job(AvahiQueryScheduler *s, AvahiKey *key) {
297     AvahiQueryJob *qj;
298     
299     g_assert(s);
300     g_assert(key);
301
302     for (qj = s->history; qj; qj = qj->jobs_next) {
303         g_assert(qj->done);
304
305         if (avahi_key_equal(qj->key, key)) {
306             /* Check whether this entry is outdated */
307
308             if (avahi_age(&qj->delivery) > AVAHI_QUERY_HISTORY_MSEC*1000) {
309                 /* it is outdated, so let's remove it */
310                 job_free(s, qj);
311                 return NULL;
312             }
313                 
314             return qj;
315         }
316     }
317
318     return NULL;
319 }
320
321 gboolean avahi_query_scheduler_post(AvahiQueryScheduler *s, AvahiKey *key, gboolean immediately) {
322     GTimeVal tv;
323     AvahiQueryJob *qj;
324     
325     g_assert(s);
326     g_assert(key);
327
328     if ((qj = find_history_job(s, key))) {
329 /*         g_message("Query suppressed by local duplicate suppression (history)"); */
330         return FALSE;
331     }
332     
333     avahi_elapse_time(&tv, immediately ? 0 : AVAHI_QUERY_DEFER_MSEC, 0);
334
335     if ((qj = find_scheduled_job(s, key))) {
336         /* Duplicate questions suppression */
337
338 /*         g_message("Query suppressed by local duplicate suppression (scheduled)"); */
339         
340         if (avahi_timeval_compare(&tv, &qj->delivery) < 0) {
341             /* If the new entry should be scheduled earlier,
342              * update the old entry */
343             qj->delivery = tv;
344             avahi_time_event_queue_update(s->time_event_queue, qj->time_event, &qj->delivery);
345         }
346
347         return TRUE;
348     } else {
349 /*         g_message("Accepted new query job.\n"); */
350
351         qj = job_new(s, key, FALSE);
352         qj->delivery = tv;
353         qj->time_event = avahi_time_event_queue_add(s->time_event_queue, &qj->delivery, elapse_callback, qj);
354         
355         return TRUE;
356     }
357 }
358
359 void avahi_query_scheduler_incoming(AvahiQueryScheduler *s, AvahiKey *key) {
360     AvahiQueryJob *qj;
361     
362     g_assert(s);
363     g_assert(key);
364
365     /* This function is called whenever an incoming query was
366      * receieved. We drop scheduled queries that match. The keyword is
367      * "DUPLICATE QUESTION SUPPRESION". */
368
369     if ((qj = find_scheduled_job(s, key))) {
370 /*         g_message("Query suppressed by distributed duplicate suppression"); */
371         job_mark_done(s, qj);
372         return;
373     }
374     
375     qj = job_new(s, key, TRUE);
376     g_get_current_time(&qj->delivery);
377     job_set_elapse_time(s, qj, AVAHI_QUERY_HISTORY_MSEC, 0);
378 }
379