// Copyright 2003-2005 Arthur van Hoff, Rick Blair // Licensed under Apache License version 2.0 // Original license LGPL package javax.jmdns.impl.tasks; import java.util.HashSet; import java.util.Set; import java.util.Timer; import java.util.logging.Level; import java.util.logging.Logger; import javax.jmdns.impl.DNSIncoming; import javax.jmdns.impl.DNSOutgoing; import javax.jmdns.impl.DNSQuestion; import javax.jmdns.impl.DNSRecord; import javax.jmdns.impl.JmDNSImpl; import javax.jmdns.impl.constants.DNSConstants; /** * The Responder sends a single answer for the specified service infos and for the host name. */ public class Responder extends DNSTask { static Logger logger = Logger.getLogger(Responder.class.getName()); /** * */ private final DNSIncoming _in; /** * */ private final boolean _unicast; public Responder(JmDNSImpl jmDNSImpl, DNSIncoming in, int port) { super(jmDNSImpl); this._in = in; this._unicast = (port != DNSConstants.MDNS_PORT); } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.DNSTask#getName() */ @Override public String getName() { return "Responder(" + (this.getDns() != null ? this.getDns().getName() : "") + ")"; } /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return super.toString() + " incomming: " + _in; } /* * (non-Javadoc) * @see javax.jmdns.impl.tasks.DNSTask#start(java.util.Timer) */ @Override public void start(Timer timer) { // According to draft-cheshire-dnsext-multicastdns.txt chapter "7 Responding": // We respond immediately if we know for sure, that we are the only one who can respond to the query. // In all other cases, we respond within 20-120 ms. // // According to draft-cheshire-dnsext-multicastdns.txt chapter "6.2 Multi-Packet Known Answer Suppression": // We respond after 20-120 ms if the query is truncated. boolean iAmTheOnlyOne = true; for (DNSQuestion question : _in.getQuestions()) { if (logger.isLoggable(Level.FINEST)) { logger.finest(this.getName() + "start() question=" + question); } iAmTheOnlyOne = question.iAmTheOnlyOne(this.getDns()); if (!iAmTheOnlyOne) { break; } } 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(); if (delay < 0) { delay = 0; } if (logger.isLoggable(Level.FINEST)) { logger.finest(this.getName() + "start() Responder chosen delay=" + delay); } if (!this.getDns().isCanceling() && !this.getDns().isCanceled()) { timer.schedule(this, delay); } } @Override public void run() { this.getDns().respondToQuery(_in); // We use these sets to prevent duplicate records Set questions = new HashSet(); Set answers = new HashSet(); if (this.getDns().isAnnounced()) { try { // Answer questions for (DNSQuestion question : _in.getQuestions()) { if (logger.isLoggable(Level.FINER)) { logger.finer(this.getName() + "run() JmDNS responding to: " + question); } // for unicast responses the question must be included if (_unicast) { // out.addQuestion(q); questions.add(question); } question.addAnswers(this.getDns(), answers); } // remove known answers, if the ttl is at least half of the correct value. (See Draft Cheshire chapter 7.1.). long now = System.currentTimeMillis(); for (DNSRecord knownAnswer : _in.getAnswers()) { if (knownAnswer.isStale(now)) { answers.remove(knownAnswer); if (logger.isLoggable(Level.FINER)) { logger.finer(this.getName() + "JmDNS Responder Known Answer Removed"); } } } // respond if we have answers if (!answers.isEmpty()) { if (logger.isLoggable(Level.FINER)) { logger.finer(this.getName() + "run() JmDNS responding"); } DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA, !_unicast, _in.getSenderUDPPayload()); out.setId(_in.getId()); for (DNSQuestion question : questions) { if (question != null) { out = this.addQuestion(out, question); } } for (DNSRecord answer : answers) { if (answer != null) { out = this.addAnswer(out, _in, answer); } } if (!out.isEmpty()) this.getDns().send(out); } // this.cancel(); } catch (Throwable e) { logger.log(Level.WARNING, this.getName() + "run() exception ", e); this.getDns().close(); } } } }