1// Copyright 2003-2005 Arthur van Hoff, Rick Blair
2// Licensed under Apache License version 2.0
3// Original license LGPL
4
5package javax.jmdns.impl.tasks;
6
7import java.util.HashSet;
8import java.util.Set;
9import java.util.Timer;
10import java.util.logging.Level;
11import java.util.logging.Logger;
12
13import javax.jmdns.impl.DNSIncoming;
14import javax.jmdns.impl.DNSOutgoing;
15import javax.jmdns.impl.DNSQuestion;
16import javax.jmdns.impl.DNSRecord;
17import javax.jmdns.impl.JmDNSImpl;
18import javax.jmdns.impl.constants.DNSConstants;
19
20/**
21 * The Responder sends a single answer for the specified service infos and for the host name.
22 */
23public class Responder extends DNSTask {
24    static Logger             logger = Logger.getLogger(Responder.class.getName());
25
26    /**
27     *
28     */
29    private final DNSIncoming _in;
30
31    /**
32     *
33     */
34    private final boolean     _unicast;
35
36    public Responder(JmDNSImpl jmDNSImpl, DNSIncoming in, int port) {
37        super(jmDNSImpl);
38        this._in = in;
39        this._unicast = (port != DNSConstants.MDNS_PORT);
40    }
41
42    /*
43     * (non-Javadoc)
44     * @see javax.jmdns.impl.tasks.DNSTask#getName()
45     */
46    @Override
47    public String getName() {
48        return "Responder(" + (this.getDns() != null ? this.getDns().getName() : "") + ")";
49    }
50
51    /*
52     * (non-Javadoc)
53     * @see java.lang.Object#toString()
54     */
55    @Override
56    public String toString() {
57        return super.toString() + " incomming: " + _in;
58    }
59
60    /*
61     * (non-Javadoc)
62     * @see javax.jmdns.impl.tasks.DNSTask#start(java.util.Timer)
63     */
64    @Override
65    public void start(Timer timer) {
66        // According to draft-cheshire-dnsext-multicastdns.txt chapter "7 Responding":
67        // We respond immediately if we know for sure, that we are the only one who can respond to the query.
68        // In all other cases, we respond within 20-120 ms.
69        //
70        // According to draft-cheshire-dnsext-multicastdns.txt chapter "6.2 Multi-Packet Known Answer Suppression":
71        // We respond after 20-120 ms if the query is truncated.
72
73        boolean iAmTheOnlyOne = true;
74        for (DNSQuestion question : _in.getQuestions()) {
75            if (logger.isLoggable(Level.FINEST)) {
76                logger.finest(this.getName() + "start() question=" + question);
77            }
78            iAmTheOnlyOne = question.iAmTheOnlyOne(this.getDns());
79            if (!iAmTheOnlyOne) {
80                break;
81            }
82        }
83        int delay = (iAmTheOnlyOne && !_in.isTruncated()) ? 0 : DNSConstants.RESPONSE_MIN_WAIT_INTERVAL + JmDNSImpl.getRandom().nextInt(DNSConstants.RESPONSE_MAX_WAIT_INTERVAL - DNSConstants.RESPONSE_MIN_WAIT_INTERVAL + 1) - _in.elapseSinceArrival();
84        if (delay < 0) {
85            delay = 0;
86        }
87        if (logger.isLoggable(Level.FINEST)) {
88            logger.finest(this.getName() + "start() Responder chosen delay=" + delay);
89        }
90        if (!this.getDns().isCanceling() && !this.getDns().isCanceled()) {
91            timer.schedule(this, delay);
92        }
93    }
94
95    @Override
96    public void run() {
97        this.getDns().respondToQuery(_in);
98
99        // We use these sets to prevent duplicate records
100        Set<DNSQuestion> questions = new HashSet<DNSQuestion>();
101        Set<DNSRecord> answers = new HashSet<DNSRecord>();
102
103        if (this.getDns().isAnnounced()) {
104            try {
105                // Answer questions
106                for (DNSQuestion question : _in.getQuestions()) {
107                    if (logger.isLoggable(Level.FINER)) {
108                        logger.finer(this.getName() + "run() JmDNS responding to: " + question);
109                    }
110                    // for unicast responses the question must be included
111                    if (_unicast) {
112                        // out.addQuestion(q);
113                        questions.add(question);
114                    }
115
116                    question.addAnswers(this.getDns(), answers);
117                }
118
119                // remove known answers, if the ttl is at least half of the correct value. (See Draft Cheshire chapter 7.1.).
120                long now = System.currentTimeMillis();
121                for (DNSRecord knownAnswer : _in.getAnswers()) {
122                    if (knownAnswer.isStale(now)) {
123                        answers.remove(knownAnswer);
124                        if (logger.isLoggable(Level.FINER)) {
125                            logger.finer(this.getName() + "JmDNS Responder Known Answer Removed");
126                        }
127                    }
128                }
129
130                // respond if we have answers
131                if (!answers.isEmpty()) {
132                    if (logger.isLoggable(Level.FINER)) {
133                        logger.finer(this.getName() + "run() JmDNS responding");
134                    }
135                    DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA, !_unicast, _in.getSenderUDPPayload());
136                    out.setId(_in.getId());
137                    for (DNSQuestion question : questions) {
138                        if (question != null) {
139                            out = this.addQuestion(out, question);
140                        }
141                    }
142                    for (DNSRecord answer : answers) {
143                        if (answer != null) {
144                            out = this.addAnswer(out, _in, answer);
145
146                        }
147                    }
148                    if (!out.isEmpty()) this.getDns().send(out);
149                }
150                // this.cancel();
151            } catch (Throwable e) {
152                logger.log(Level.WARNING, this.getName() + "run() exception ", e);
153                this.getDns().close();
154            }
155        }
156    }
157}