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 <stdlib.h>
27
28#include <avahi-common/domain.h>
29#include <avahi-common/timeval.h>
30#include <avahi-common/malloc.h>
31
32#include "probe-sched.h"
33#include "log.h"
34#include "rr-util.h"
35
36#define AVAHI_PROBE_HISTORY_MSEC 150
37#define AVAHI_PROBE_DEFER_MSEC 50
38
39typedef struct AvahiProbeJob AvahiProbeJob;
40
41struct AvahiProbeJob {
42    AvahiProbeScheduler *scheduler;
43    AvahiTimeEvent *time_event;
44
45    int chosen; /* Use for packet assembling */
46    int done;
47    struct timeval delivery;
48
49    AvahiRecord *record;
50
51    AVAHI_LLIST_FIELDS(AvahiProbeJob, jobs);
52};
53
54struct AvahiProbeScheduler {
55    AvahiInterface *interface;
56    AvahiTimeEventQueue *time_event_queue;
57
58    AVAHI_LLIST_HEAD(AvahiProbeJob, jobs);
59    AVAHI_LLIST_HEAD(AvahiProbeJob, history);
60};
61
62static AvahiProbeJob* job_new(AvahiProbeScheduler *s, AvahiRecord *record, int done) {
63    AvahiProbeJob *pj;
64
65    assert(s);
66    assert(record);
67
68    if (!(pj = avahi_new(AvahiProbeJob, 1))) {
69        avahi_log_error(__FILE__": Out of memory");
70        return NULL; /* OOM */
71    }
72
73    pj->scheduler = s;
74    pj->record = avahi_record_ref(record);
75    pj->time_event = NULL;
76    pj->chosen = 0;
77
78    if ((pj->done = done))
79        AVAHI_LLIST_PREPEND(AvahiProbeJob, jobs, s->history, pj);
80    else
81        AVAHI_LLIST_PREPEND(AvahiProbeJob, jobs, s->jobs, pj);
82
83    return pj;
84}
85
86static void job_free(AvahiProbeScheduler *s, AvahiProbeJob *pj) {
87    assert(pj);
88
89    if (pj->time_event)
90        avahi_time_event_free(pj->time_event);
91
92    if (pj->done)
93        AVAHI_LLIST_REMOVE(AvahiProbeJob, jobs, s->history, pj);
94    else
95        AVAHI_LLIST_REMOVE(AvahiProbeJob, jobs, s->jobs, pj);
96
97    avahi_record_unref(pj->record);
98    avahi_free(pj);
99}
100
101static void elapse_callback(AvahiTimeEvent *e, void* data);
102
103static void job_set_elapse_time(AvahiProbeScheduler *s, AvahiProbeJob *pj, unsigned msec, unsigned jitter) {
104    struct timeval tv;
105
106    assert(s);
107    assert(pj);
108
109    avahi_elapse_time(&tv, msec, jitter);
110
111    if (pj->time_event)
112        avahi_time_event_update(pj->time_event, &tv);
113    else
114        pj->time_event = avahi_time_event_new(s->time_event_queue, &tv, elapse_callback, pj);
115}
116
117static void job_mark_done(AvahiProbeScheduler *s, AvahiProbeJob *pj) {
118    assert(s);
119    assert(pj);
120
121    assert(!pj->done);
122
123    AVAHI_LLIST_REMOVE(AvahiProbeJob, jobs, s->jobs, pj);
124    AVAHI_LLIST_PREPEND(AvahiProbeJob, jobs, s->history, pj);
125
126    pj->done = 1;
127
128    job_set_elapse_time(s, pj, AVAHI_PROBE_HISTORY_MSEC, 0);
129    gettimeofday(&pj->delivery, NULL);
130}
131
132AvahiProbeScheduler *avahi_probe_scheduler_new(AvahiInterface *i) {
133    AvahiProbeScheduler *s;
134
135    assert(i);
136
137    if (!(s = avahi_new(AvahiProbeScheduler, 1))) {
138        avahi_log_error(__FILE__": Out of memory");
139        return NULL;
140    }
141
142    s->interface = i;
143    s->time_event_queue = i->monitor->server->time_event_queue;
144
145    AVAHI_LLIST_HEAD_INIT(AvahiProbeJob, s->jobs);
146    AVAHI_LLIST_HEAD_INIT(AvahiProbeJob, s->history);
147
148    return s;
149}
150
151void avahi_probe_scheduler_free(AvahiProbeScheduler *s) {
152    assert(s);
153
154    avahi_probe_scheduler_clear(s);
155    avahi_free(s);
156}
157
158void avahi_probe_scheduler_clear(AvahiProbeScheduler *s) {
159    assert(s);
160
161    while (s->jobs)
162        job_free(s, s->jobs);
163    while (s->history)
164        job_free(s, s->history);
165}
166
167static int packet_add_probe_query(AvahiProbeScheduler *s, AvahiDnsPacket *p, AvahiProbeJob *pj) {
168    size_t size;
169    AvahiKey *k;
170    int b;
171
172    assert(s);
173    assert(p);
174    assert(pj);
175
176    assert(!pj->chosen);
177
178    /* Estimate the size for this record */
179    size =
180        avahi_key_get_estimate_size(pj->record->key) +
181        avahi_record_get_estimate_size(pj->record);
182
183    /* Too large */
184    if (size > avahi_dns_packet_space(p))
185        return 0;
186
187    /* Create the probe query */
188    if (!(k = avahi_key_new(pj->record->key->name, pj->record->key->clazz, AVAHI_DNS_TYPE_ANY)))
189        return 0; /* OOM */
190
191    b = !!avahi_dns_packet_append_key(p, k, 0);
192    assert(b);
193
194    /* Mark this job for addition to the packet */
195    pj->chosen = 1;
196
197    /* Scan for more jobs whith matching key pattern */
198    for (pj = s->jobs; pj; pj = pj->jobs_next) {
199        if (pj->chosen)
200            continue;
201
202        /* Does the record match the probe? */
203        if (k->clazz != pj->record->key->clazz || !avahi_domain_equal(k->name, pj->record->key->name))
204            continue;
205
206        /* This job wouldn't fit in */
207        if (avahi_record_get_estimate_size(pj->record) > avahi_dns_packet_space(p))
208            break;
209
210        /* Mark this job for addition to the packet */
211        pj->chosen = 1;
212    }
213
214    avahi_key_unref(k);
215
216    return 1;
217}
218
219static void elapse_callback(AVAHI_GCC_UNUSED AvahiTimeEvent *e, void* data) {
220    AvahiProbeJob *pj = data, *next;
221    AvahiProbeScheduler *s;
222    AvahiDnsPacket *p;
223    unsigned n;
224
225    assert(pj);
226    s = pj->scheduler;
227
228    if (pj->done) {
229        /* Lets remove it  from the history */
230        job_free(s, pj);
231        return;
232    }
233
234    if (!(p = avahi_dns_packet_new_query(s->interface->hardware->mtu)))
235        return; /* OOM */
236    n = 1;
237
238    /* Add the import probe */
239    if (!packet_add_probe_query(s, p, pj)) {
240        size_t size;
241        AvahiKey *k;
242        int b;
243
244        avahi_dns_packet_free(p);
245
246        /* The probe didn't fit in the package, so let's allocate a larger one */
247
248        size =
249            avahi_key_get_estimate_size(pj->record->key) +
250            avahi_record_get_estimate_size(pj->record) +
251            AVAHI_DNS_PACKET_HEADER_SIZE;
252
253        if (!(p = avahi_dns_packet_new_query(size + AVAHI_DNS_PACKET_EXTRA_SIZE)))
254            return; /* OOM */
255
256        if (!(k = avahi_key_new(pj->record->key->name, pj->record->key->clazz, AVAHI_DNS_TYPE_ANY))) {
257            avahi_dns_packet_free(p);
258            return;  /* OOM */
259        }
260
261        b = avahi_dns_packet_append_key(p, k, 0) && avahi_dns_packet_append_record(p, pj->record, 0, 0);
262        avahi_key_unref(k);
263
264        if (b) {
265            avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_NSCOUNT, 1);
266            avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_QDCOUNT, 1);
267            avahi_interface_send_packet(s->interface, p);
268        } else
269            avahi_log_warn("Probe record too large, cannot send");
270
271        avahi_dns_packet_free(p);
272        job_mark_done(s, pj);
273
274        return;
275    }
276
277    /* Try to fill up packet with more probes, if available */
278    for (pj = s->jobs; pj; pj = pj->jobs_next) {
279
280        if (pj->chosen)
281            continue;
282
283        if (!packet_add_probe_query(s, p, pj))
284            break;
285
286        n++;
287    }
288
289    avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_QDCOUNT, n);
290
291    n = 0;
292
293    /* Now add the chosen records to the authorative section */
294    for (pj = s->jobs; pj; pj = next) {
295
296        next = pj->jobs_next;
297
298        if (!pj->chosen)
299            continue;
300
301        if (!avahi_dns_packet_append_record(p, pj->record, 0, 0)) {
302/*             avahi_log_warn("Bad probe size estimate!"); */
303
304            /* Unmark all following jobs */
305            for (; pj; pj = pj->jobs_next)
306                pj->chosen = 0;
307
308            break;
309        }
310
311        job_mark_done(s, pj);
312
313        n ++;
314    }
315
316    avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_NSCOUNT, n);
317
318    /* Send it now */
319    avahi_interface_send_packet(s->interface, p);
320    avahi_dns_packet_free(p);
321}
322
323static AvahiProbeJob* find_scheduled_job(AvahiProbeScheduler *s, AvahiRecord *record) {
324    AvahiProbeJob *pj;
325
326    assert(s);
327    assert(record);
328
329    for (pj = s->jobs; pj; pj = pj->jobs_next) {
330        assert(!pj->done);
331
332        if (avahi_record_equal_no_ttl(pj->record, record))
333            return pj;
334    }
335
336    return NULL;
337}
338
339static AvahiProbeJob* find_history_job(AvahiProbeScheduler *s, AvahiRecord *record) {
340    AvahiProbeJob *pj;
341
342    assert(s);
343    assert(record);
344
345    for (pj = s->history; pj; pj = pj->jobs_next) {
346        assert(pj->done);
347
348        if (avahi_record_equal_no_ttl(pj->record, record)) {
349            /* Check whether this entry is outdated */
350
351            if (avahi_age(&pj->delivery) > AVAHI_PROBE_HISTORY_MSEC*1000) {
352                /* it is outdated, so let's remove it */
353                job_free(s, pj);
354                return NULL;
355            }
356
357            return pj;
358        }
359    }
360
361    return NULL;
362}
363
364int avahi_probe_scheduler_post(AvahiProbeScheduler *s, AvahiRecord *record, int immediately) {
365    AvahiProbeJob *pj;
366    struct timeval tv;
367
368    assert(s);
369    assert(record);
370    assert(!avahi_key_is_pattern(record->key));
371
372    if ((pj = find_history_job(s, record)))
373        return 0;
374
375    avahi_elapse_time(&tv, immediately ? 0 : AVAHI_PROBE_DEFER_MSEC, 0);
376
377    if ((pj = find_scheduled_job(s, record))) {
378
379        if (avahi_timeval_compare(&tv, &pj->delivery) < 0) {
380            /* If the new entry should be scheduled earlier, update the old entry */
381            pj->delivery = tv;
382            avahi_time_event_update(pj->time_event, &pj->delivery);
383        }
384
385        return 1;
386    } else {
387        /* Create a new job and schedule it */
388        if (!(pj = job_new(s, record, 0)))
389            return 0; /* OOM */
390
391        pj->delivery = tv;
392        pj->time_event = avahi_time_event_new(s->time_event_queue, &pj->delivery, elapse_callback, pj);
393
394
395/*     avahi_log_debug("Accepted new probe job."); */
396
397        return 1;
398    }
399}
400