13742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman// Copyright 2003-2005 Arthur van Hoff, Rick Blair
23742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman// Licensed under Apache License version 2.0
33742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman// Original license LGPL
43742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman
53742d9db8b6edb10627b0f89336cca5249f1d15aManuel Romanpackage javax.jmdns.impl.tasks;
63742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman
73742d9db8b6edb10627b0f89336cca5249f1d15aManuel Romanimport java.util.HashSet;
83742d9db8b6edb10627b0f89336cca5249f1d15aManuel Romanimport java.util.Set;
93742d9db8b6edb10627b0f89336cca5249f1d15aManuel Romanimport java.util.Timer;
103742d9db8b6edb10627b0f89336cca5249f1d15aManuel Romanimport java.util.logging.Level;
113742d9db8b6edb10627b0f89336cca5249f1d15aManuel Romanimport java.util.logging.Logger;
123742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman
133742d9db8b6edb10627b0f89336cca5249f1d15aManuel Romanimport javax.jmdns.impl.DNSIncoming;
143742d9db8b6edb10627b0f89336cca5249f1d15aManuel Romanimport javax.jmdns.impl.DNSOutgoing;
153742d9db8b6edb10627b0f89336cca5249f1d15aManuel Romanimport javax.jmdns.impl.DNSQuestion;
163742d9db8b6edb10627b0f89336cca5249f1d15aManuel Romanimport javax.jmdns.impl.DNSRecord;
173742d9db8b6edb10627b0f89336cca5249f1d15aManuel Romanimport javax.jmdns.impl.JmDNSImpl;
183742d9db8b6edb10627b0f89336cca5249f1d15aManuel Romanimport javax.jmdns.impl.constants.DNSConstants;
193742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman
203742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman/**
213742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman * The Responder sends a single answer for the specified service infos and for the host name.
223742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman */
233742d9db8b6edb10627b0f89336cca5249f1d15aManuel Romanpublic class Responder extends DNSTask {
243742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman    static Logger             logger = Logger.getLogger(Responder.class.getName());
253742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman
263742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman    /**
273742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman     *
283742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman     */
293742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman    private final DNSIncoming _in;
303742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman
313742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman    /**
323742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman     *
333742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman     */
343742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman    private final boolean     _unicast;
353742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman
363742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman    public Responder(JmDNSImpl jmDNSImpl, DNSIncoming in, int port) {
373742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman        super(jmDNSImpl);
383742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman        this._in = in;
393742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman        this._unicast = (port != DNSConstants.MDNS_PORT);
403742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman    }
413742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman
423742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman    /*
433742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman     * (non-Javadoc)
443742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman     * @see javax.jmdns.impl.tasks.DNSTask#getName()
453742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman     */
463742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman    @Override
473742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman    public String getName() {
483742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman        return "Responder(" + (this.getDns() != null ? this.getDns().getName() : "") + ")";
493742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman    }
503742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman
513742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman    /*
523742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman     * (non-Javadoc)
533742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman     * @see java.lang.Object#toString()
543742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman     */
553742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman    @Override
563742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman    public String toString() {
573742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman        return super.toString() + " incomming: " + _in;
583742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman    }
593742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman
603742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman    /*
613742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman     * (non-Javadoc)
623742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman     * @see javax.jmdns.impl.tasks.DNSTask#start(java.util.Timer)
633742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman     */
643742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman    @Override
653742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman    public void start(Timer timer) {
663742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman        // According to draft-cheshire-dnsext-multicastdns.txt chapter "7 Responding":
673742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman        // We respond immediately if we know for sure, that we are the only one who can respond to the query.
683742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman        // In all other cases, we respond within 20-120 ms.
693742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman        //
703742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman        // According to draft-cheshire-dnsext-multicastdns.txt chapter "6.2 Multi-Packet Known Answer Suppression":
713742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman        // We respond after 20-120 ms if the query is truncated.
723742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman
733742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman        boolean iAmTheOnlyOne = true;
743742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman        for (DNSQuestion question : _in.getQuestions()) {
753742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman            if (logger.isLoggable(Level.FINEST)) {
763742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                logger.finest(this.getName() + "start() question=" + question);
773742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman            }
783742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman            iAmTheOnlyOne = question.iAmTheOnlyOne(this.getDns());
793742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman            if (!iAmTheOnlyOne) {
803742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                break;
813742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman            }
823742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman        }
833742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman        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();
843742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman        if (delay < 0) {
853742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman            delay = 0;
863742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman        }
873742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman        if (logger.isLoggable(Level.FINEST)) {
883742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman            logger.finest(this.getName() + "start() Responder chosen delay=" + delay);
893742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman        }
903742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman        if (!this.getDns().isCanceling() && !this.getDns().isCanceled()) {
913742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman            timer.schedule(this, delay);
923742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman        }
933742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman    }
943742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman
953742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman    @Override
963742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman    public void run() {
973742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman        this.getDns().respondToQuery(_in);
983742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman
993742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman        // We use these sets to prevent duplicate records
1003742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman        Set<DNSQuestion> questions = new HashSet<DNSQuestion>();
1013742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman        Set<DNSRecord> answers = new HashSet<DNSRecord>();
1023742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman
1033742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman        if (this.getDns().isAnnounced()) {
1043742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman            try {
1053742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                // Answer questions
1063742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                for (DNSQuestion question : _in.getQuestions()) {
1073742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                    if (logger.isLoggable(Level.FINER)) {
1083742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                        logger.finer(this.getName() + "run() JmDNS responding to: " + question);
1093742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                    }
1103742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                    // for unicast responses the question must be included
1113742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                    if (_unicast) {
1123742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                        // out.addQuestion(q);
1133742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                        questions.add(question);
1143742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                    }
1153742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman
1163742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                    question.addAnswers(this.getDns(), answers);
1173742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                }
1183742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman
1193742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                // remove known answers, if the ttl is at least half of the correct value. (See Draft Cheshire chapter 7.1.).
1203742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                long now = System.currentTimeMillis();
1213742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                for (DNSRecord knownAnswer : _in.getAnswers()) {
1223742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                    if (knownAnswer.isStale(now)) {
1233742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                        answers.remove(knownAnswer);
1243742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                        if (logger.isLoggable(Level.FINER)) {
1253742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                            logger.finer(this.getName() + "JmDNS Responder Known Answer Removed");
1263742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                        }
1273742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                    }
1283742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                }
1293742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman
1303742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                // respond if we have answers
1313742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                if (!answers.isEmpty()) {
1323742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                    if (logger.isLoggable(Level.FINER)) {
1333742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                        logger.finer(this.getName() + "run() JmDNS responding");
1343742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                    }
1353742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                    DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA, !_unicast, _in.getSenderUDPPayload());
1363742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                    out.setId(_in.getId());
1373742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                    for (DNSQuestion question : questions) {
1383742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                        if (question != null) {
1393742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                            out = this.addQuestion(out, question);
1403742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                        }
1413742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                    }
1423742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                    for (DNSRecord answer : answers) {
1433742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                        if (answer != null) {
1443742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                            out = this.addAnswer(out, _in, answer);
1453742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman
1463742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                        }
1473742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                    }
1483742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                    if (!out.isEmpty()) this.getDns().send(out);
1493742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                }
1503742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                // this.cancel();
1513742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman            } catch (Throwable e) {
1523742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                logger.log(Level.WARNING, this.getName() + "run() exception ", e);
1533742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman                this.getDns().close();
1543742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman            }
1553742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman        }
1563742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman    }
1573742d9db8b6edb10627b0f89336cca5249f1d15aManuel Roman}