beremiz

Parents d0f7d36bf241
Children aae70ef5af6d
Fixed bad bahavior when zeroconf bindaddress unspecified.
  • +1236 -1233
    Zeroconf.py
  • --- a/Zeroconf.py Mon Aug 25 18:03:10 2008 +0200
    +++ b/Zeroconf.py Tue Aug 26 13:47:21 2008 +0200
    @@ -19,33 +19,33 @@
    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    -
    +
    """
    """0.12 update - allow selection of binding interface
    - typo fix - Thanks A. M. Kuchlingi
    - removed all use of word 'Rendezvous' - this is an API change"""
    + typo fix - Thanks A. M. Kuchlingi
    + removed all use of word 'Rendezvous' - this is an API change"""
    """0.11 update - correction to comments for addListener method
    support for new record types seen from OS X
    - - IPv6 address
    - - hostinfo
    - ignore unknown DNS record types
    - fixes to name decoding
    - works alongside other processes using port 5353 (e.g. on Mac OS X)
    - tested against Mac OS X 10.3.2's mDNSResponder
    - corrections to removal of list entries for service browser"""
    + - IPv6 address
    + - hostinfo
    + ignore unknown DNS record types
    + fixes to name decoding
    + works alongside other processes using port 5353 (e.g. on Mac OS X)
    + tested against Mac OS X 10.3.2's mDNSResponder
    + corrections to removal of list entries for service browser"""
    """0.10 update - Jonathon Paisley contributed these corrections:
    always multicast replies, even when query is unicast
    - correct a pointer encoding problem
    - can now write records in any order
    - traceback shown on failure
    - better TXT record parsing
    - server is now separate from name
    - can cancel a service browser
    + correct a pointer encoding problem
    + can now write records in any order
    + traceback shown on failure
    + better TXT record parsing
    + server is now separate from name
    + can cancel a service browser
    - modified some unit tests to accommodate these changes"""
    + modified some unit tests to accommodate these changes"""
    """0.09 update - remove all records on service unregistration
    fix DOS security problem with readName"""
    @@ -54,25 +54,25 @@
    """0.07 update - faster shutdown on engine
    pointer encoding of outgoing names
    - ServiceBrowser now works
    - new unit tests"""
    + ServiceBrowser now works
    + new unit tests"""
    """0.06 update - small improvements with unit tests
    added defined exception types
    - new style objects
    - fixed hostname/interface problem
    - fixed socket timeout problem
    - fixed addServiceListener() typo bug
    - using select() for socket reads
    - tested on Debian unstable with Python 2.2.2"""
    + new style objects
    + fixed hostname/interface problem
    + fixed socket timeout problem
    + fixed addServiceListener() typo bug
    + using select() for socket reads
    + tested on Debian unstable with Python 2.2.2"""
    """0.05 update - ensure case insensitivty on domain names
    support for unicast DNS queries"""
    """0.04 update - added some unit tests
    added __ne__ adjuncts where required
    - ensure names end in '.local.'
    - timeout on receiving socket for clean shutdown"""
    + ensure names end in '.local.'
    + timeout on receiving socket for clean shutdown"""
    __author__ = "Paul Scott-Murphy"
    __email__ = "paul at scott dash murphy dot com"
    @@ -101,7 +101,7 @@
    _BROWSER_TIME = 500
    # Some DNS constants
    -
    +
    _MDNS_ADDR = '224.0.0.251'
    _MDNS_PORT = 5353;
    _DNS_PORT = 53;
    @@ -155,1402 +155,1405 @@
    # Mapping constants to names
    _CLASSES = { _CLASS_IN : "in",
    - _CLASS_CS : "cs",
    - _CLASS_CH : "ch",
    - _CLASS_HS : "hs",
    - _CLASS_NONE : "none",
    - _CLASS_ANY : "any" }
    + _CLASS_CS : "cs",
    + _CLASS_CH : "ch",
    + _CLASS_HS : "hs",
    + _CLASS_NONE : "none",
    + _CLASS_ANY : "any" }
    _TYPES = { _TYPE_A : "a",
    - _TYPE_NS : "ns",
    - _TYPE_MD : "md",
    - _TYPE_MF : "mf",
    - _TYPE_CNAME : "cname",
    - _TYPE_SOA : "soa",
    - _TYPE_MB : "mb",
    - _TYPE_MG : "mg",
    - _TYPE_MR : "mr",
    - _TYPE_NULL : "null",
    - _TYPE_WKS : "wks",
    - _TYPE_PTR : "ptr",
    - _TYPE_HINFO : "hinfo",
    - _TYPE_MINFO : "minfo",
    - _TYPE_MX : "mx",
    - _TYPE_TXT : "txt",
    - _TYPE_AAAA : "quada",
    - _TYPE_SRV : "srv",
    - _TYPE_ANY : "any" }
    + _TYPE_NS : "ns",
    + _TYPE_MD : "md",
    + _TYPE_MF : "mf",
    + _TYPE_CNAME : "cname",
    + _TYPE_SOA : "soa",
    + _TYPE_MB : "mb",
    + _TYPE_MG : "mg",
    + _TYPE_MR : "mr",
    + _TYPE_NULL : "null",
    + _TYPE_WKS : "wks",
    + _TYPE_PTR : "ptr",
    + _TYPE_HINFO : "hinfo",
    + _TYPE_MINFO : "minfo",
    + _TYPE_MX : "mx",
    + _TYPE_TXT : "txt",
    + _TYPE_AAAA : "quada",
    + _TYPE_SRV : "srv",
    + _TYPE_ANY : "any" }
    # utility functions
    def currentTimeMillis():
    - """Current system time in milliseconds"""
    - return time.time() * 1000
    + """Current system time in milliseconds"""
    + return time.time() * 1000
    # Exceptions
    class NonLocalNameException(Exception):
    - pass
    + pass
    class NonUniqueNameException(Exception):
    - pass
    + pass
    class NamePartTooLongException(Exception):
    - pass
    + pass
    class AbstractMethodException(Exception):
    - pass
    + pass
    class BadTypeInNameException(Exception):
    - pass
    + pass
    # implementation classes
    class DNSEntry(object):
    - """A DNS entry"""
    -
    - def __init__(self, name, type, clazz):
    - self.key = string.lower(name)
    - self.name = name
    - self.type = type
    - self.clazz = clazz & _CLASS_MASK
    - self.unique = (clazz & _CLASS_UNIQUE) != 0
    + """A DNS entry"""
    +
    + def __init__(self, name, type, clazz):
    + self.key = string.lower(name)
    + self.name = name
    + self.type = type
    + self.clazz = clazz & _CLASS_MASK
    + self.unique = (clazz & _CLASS_UNIQUE) != 0
    - def __eq__(self, other):
    - """Equality test on name, type, and class"""
    - if isinstance(other, DNSEntry):
    - return self.name == other.name and self.type == other.type and self.clazz == other.clazz
    - return 0
    + def __eq__(self, other):
    + """Equality test on name, type, and class"""
    + if isinstance(other, DNSEntry):
    + return self.name == other.name and self.type == other.type and self.clazz == other.clazz
    + return 0
    - def __ne__(self, other):
    - """Non-equality test"""
    - return not self.__eq__(other)
    + def __ne__(self, other):
    + """Non-equality test"""
    + return not self.__eq__(other)
    - def getClazz(self, clazz):
    - """Class accessor"""
    - try:
    - return _CLASSES[clazz]
    - except:
    - return "?(%s)" % (clazz)
    + def getClazz(self, clazz):
    + """Class accessor"""
    + try:
    + return _CLASSES[clazz]
    + except:
    + return "?(%s)" % (clazz)
    - def getType(self, type):
    - """Type accessor"""
    - try:
    - return _TYPES[type]
    - except:
    - return "?(%s)" % (type)
    + def getType(self, type):
    + """Type accessor"""
    + try:
    + return _TYPES[type]
    + except:
    + return "?(%s)" % (type)
    - def toString(self, hdr, other):
    - """String representation with additional information"""
    - result = "%s[%s,%s" % (hdr, self.getType(self.type), self.getClazz(self.clazz))
    - if self.unique:
    - result += "-unique,"
    - else:
    - result += ","
    - result += self.name
    - if other is not None:
    - result += ",%s]" % (other)
    - else:
    - result += "]"
    - return result
    + def toString(self, hdr, other):
    + """String representation with additional information"""
    + result = "%s[%s,%s" % (hdr, self.getType(self.type), self.getClazz(self.clazz))
    + if self.unique:
    + result += "-unique,"
    + else:
    + result += ","
    + result += self.name
    + if other is not None:
    + result += ",%s]" % (other)
    + else:
    + result += "]"
    + return result
    class DNSQuestion(DNSEntry):
    - """A DNS question entry"""
    -
    - def __init__(self, name, type, clazz):
    - if not name.endswith(".local."):
    - raise NonLocalNameException
    - DNSEntry.__init__(self, name, type, clazz)
    + """A DNS question entry"""
    +
    + def __init__(self, name, type, clazz):
    + if not name.endswith(".local."):
    + raise NonLocalNameException
    + DNSEntry.__init__(self, name, type, clazz)
    - def answeredBy(self, rec):
    - """Returns true if the question is answered by the record"""
    - return self.clazz == rec.clazz and (self.type == rec.type or self.type == _TYPE_ANY) and self.name == rec.name
    + def answeredBy(self, rec):
    + """Returns true if the question is answered by the record"""
    + return self.clazz == rec.clazz and (self.type == rec.type or self.type == _TYPE_ANY) and self.name == rec.name
    - def __repr__(self):
    - """String representation"""
    - return DNSEntry.toString(self, "question", None)
    + def __repr__(self):
    + """String representation"""
    + return DNSEntry.toString(self, "question", None)
    class DNSRecord(DNSEntry):
    - """A DNS record - like a DNS entry, but has a TTL"""
    -
    - def __init__(self, name, type, clazz, ttl):
    - DNSEntry.__init__(self, name, type, clazz)
    - self.ttl = ttl
    - self.created = currentTimeMillis()
    + """A DNS record - like a DNS entry, but has a TTL"""
    +
    + def __init__(self, name, type, clazz, ttl):
    + DNSEntry.__init__(self, name, type, clazz)
    + self.ttl = ttl
    + self.created = currentTimeMillis()
    - def __eq__(self, other):
    - """Tests equality as per DNSRecord"""
    - if isinstance(other, DNSRecord):
    - return DNSEntry.__eq__(self, other)
    - return 0
    + def __eq__(self, other):
    + """Tests equality as per DNSRecord"""
    + if isinstance(other, DNSRecord):
    + return DNSEntry.__eq__(self, other)
    + return 0
    - def suppressedBy(self, msg):
    - """Returns true if any answer in a message can suffice for the
    - information held in this record."""
    - for record in msg.answers:
    - if self.suppressedByAnswer(record):
    - return 1
    - return 0
    + def suppressedBy(self, msg):
    + """Returns true if any answer in a message can suffice for the
    + information held in this record."""
    + for record in msg.answers:
    + if self.suppressedByAnswer(record):
    + return 1
    + return 0
    - def suppressedByAnswer(self, other):
    - """Returns true if another record has same name, type and class,
    - and if its TTL is at least half of this record's."""
    - if self == other and other.ttl > (self.ttl / 2):
    - return 1
    - return 0
    + def suppressedByAnswer(self, other):
    + """Returns true if another record has same name, type and class,
    + and if its TTL is at least half of this record's."""
    + if self == other and other.ttl > (self.ttl / 2):
    + return 1
    + return 0
    - def getExpirationTime(self, percent):
    - """Returns the time at which this record will have expired
    - by a certain percentage."""
    - return self.created + (percent * self.ttl * 10)
    + def getExpirationTime(self, percent):
    + """Returns the time at which this record will have expired
    + by a certain percentage."""
    + return self.created + (percent * self.ttl * 10)
    - def getRemainingTTL(self, now):
    - """Returns the remaining TTL in seconds."""
    - return max(0, (self.getExpirationTime(100) - now) / 1000)
    + def getRemainingTTL(self, now):
    + """Returns the remaining TTL in seconds."""
    + return max(0, (self.getExpirationTime(100) - now) / 1000)
    - def isExpired(self, now):
    - """Returns true if this record has expired."""
    - return self.getExpirationTime(100) <= now
    + def isExpired(self, now):
    + """Returns true if this record has expired."""
    + return self.getExpirationTime(100) <= now
    - def isStale(self, now):
    - """Returns true if this record is at least half way expired."""
    - return self.getExpirationTime(50) <= now
    + def isStale(self, now):
    + """Returns true if this record is at least half way expired."""
    + return self.getExpirationTime(50) <= now
    - def resetTTL(self, other):
    - """Sets this record's TTL and created time to that of
    - another record."""
    - self.created = other.created
    - self.ttl = other.ttl
    + def resetTTL(self, other):
    + """Sets this record's TTL and created time to that of
    + another record."""
    + self.created = other.created
    + self.ttl = other.ttl
    - def write(self, out):
    - """Abstract method"""
    - raise AbstractMethodException
    + def write(self, out):
    + """Abstract method"""
    + raise AbstractMethodException
    - def toString(self, other):
    - """String representation with addtional information"""
    - arg = "%s/%s,%s" % (self.ttl, self.getRemainingTTL(currentTimeMillis()), other)
    - return DNSEntry.toString(self, "record", arg)
    + def toString(self, other):
    + """String representation with addtional information"""
    + arg = "%s/%s,%s" % (self.ttl, self.getRemainingTTL(currentTimeMillis()), other)
    + return DNSEntry.toString(self, "record", arg)
    class DNSAddress(DNSRecord):
    - """A DNS address record"""
    -
    - def __init__(self, name, type, clazz, ttl, address):
    - DNSRecord.__init__(self, name, type, clazz, ttl)
    - self.address = address
    + """A DNS address record"""
    +
    + def __init__(self, name, type, clazz, ttl, address):
    + DNSRecord.__init__(self, name, type, clazz, ttl)
    + self.address = address
    - def write(self, out):
    - """Used in constructing an outgoing packet"""
    - out.writeString(self.address, len(self.address))
    + def write(self, out):
    + """Used in constructing an outgoing packet"""
    + out.writeString(self.address, len(self.address))
    - def __eq__(self, other):
    - """Tests equality on address"""
    - if isinstance(other, DNSAddress):
    - return self.address == other.address
    - return 0
    + def __eq__(self, other):
    + """Tests equality on address"""
    + if isinstance(other, DNSAddress):
    + return self.address == other.address
    + return 0
    - def __repr__(self):
    - """String representation"""
    - try:
    - return socket.inet_ntoa(self.address)
    - except:
    - return self.address
    + def __repr__(self):
    + """String representation"""
    + try:
    + return socket.inet_ntoa(self.address)
    + except:
    + return self.address
    class DNSHinfo(DNSRecord):
    - """A DNS host information record"""
    + """A DNS host information record"""
    - def __init__(self, name, type, clazz, ttl, cpu, os):
    - DNSRecord.__init__(self, name, type, clazz, ttl)
    - self.cpu = cpu
    - self.os = os
    + def __init__(self, name, type, clazz, ttl, cpu, os):
    + DNSRecord.__init__(self, name, type, clazz, ttl)
    + self.cpu = cpu
    + self.os = os
    - def write(self, out):
    - """Used in constructing an outgoing packet"""
    - out.writeString(self.cpu, len(self.cpu))
    - out.writeString(self.os, len(self.os))
    + def write(self, out):
    + """Used in constructing an outgoing packet"""
    + out.writeString(self.cpu, len(self.cpu))
    + out.writeString(self.os, len(self.os))
    - def __eq__(self, other):
    - """Tests equality on cpu and os"""
    - if isinstance(other, DNSHinfo):
    - return self.cpu == other.cpu and self.os == other.os
    - return 0
    + def __eq__(self, other):
    + """Tests equality on cpu and os"""
    + if isinstance(other, DNSHinfo):
    + return self.cpu == other.cpu and self.os == other.os
    + return 0
    - def __repr__(self):
    - """String representation"""
    - return self.cpu + " " + self.os
    -
    + def __repr__(self):
    + """String representation"""
    + return self.cpu + " " + self.os
    +
    class DNSPointer(DNSRecord):
    - """A DNS pointer record"""
    -
    - def __init__(self, name, type, clazz, ttl, alias):
    - DNSRecord.__init__(self, name, type, clazz, ttl)
    - self.alias = alias
    + """A DNS pointer record"""
    +
    + def __init__(self, name, type, clazz, ttl, alias):
    + DNSRecord.__init__(self, name, type, clazz, ttl)
    + self.alias = alias
    - def write(self, out):
    - """Used in constructing an outgoing packet"""
    - out.writeName(self.alias)
    + def write(self, out):
    + """Used in constructing an outgoing packet"""
    + out.writeName(self.alias)
    - def __eq__(self, other):
    - """Tests equality on alias"""
    - if isinstance(other, DNSPointer):
    - return self.alias == other.alias
    - return 0
    + def __eq__(self, other):
    + """Tests equality on alias"""
    + if isinstance(other, DNSPointer):
    + return self.alias == other.alias
    + return 0
    - def __repr__(self):
    - """String representation"""
    - return self.toString(self.alias)
    + def __repr__(self):
    + """String representation"""
    + return self.toString(self.alias)
    class DNSText(DNSRecord):
    - """A DNS text record"""
    -
    - def __init__(self, name, type, clazz, ttl, text):
    - DNSRecord.__init__(self, name, type, clazz, ttl)
    - self.text = text
    + """A DNS text record"""
    +
    + def __init__(self, name, type, clazz, ttl, text):
    + DNSRecord.__init__(self, name, type, clazz, ttl)
    + self.text = text
    - def write(self, out):
    - """Used in constructing an outgoing packet"""
    - out.writeString(self.text, len(self.text))
    + def write(self, out):
    + """Used in constructing an outgoing packet"""
    + out.writeString(self.text, len(self.text))
    - def __eq__(self, other):
    - """Tests equality on text"""
    - if isinstance(other, DNSText):
    - return self.text == other.text
    - return 0
    + def __eq__(self, other):
    + """Tests equality on text"""
    + if isinstance(other, DNSText):
    + return self.text == other.text
    + return 0
    - def __repr__(self):
    - """String representation"""
    - if len(self.text) > 10:
    - return self.toString(self.text[:7] + "...")
    - else:
    - return self.toString(self.text)
    + def __repr__(self):
    + """String representation"""
    + if len(self.text) > 10:
    + return self.toString(self.text[:7] + "...")
    + else:
    + return self.toString(self.text)
    class DNSService(DNSRecord):
    - """A DNS service record"""
    -
    - def __init__(self, name, type, clazz, ttl, priority, weight, port, server):
    - DNSRecord.__init__(self, name, type, clazz, ttl)
    - self.priority = priority
    - self.weight = weight
    - self.port = port
    - self.server = server
    + """A DNS service record"""
    +
    + def __init__(self, name, type, clazz, ttl, priority, weight, port, server):
    + DNSRecord.__init__(self, name, type, clazz, ttl)
    + self.priority = priority
    + self.weight = weight
    + self.port = port
    + self.server = server
    - def write(self, out):
    - """Used in constructing an outgoing packet"""
    - out.writeShort(self.priority)
    - out.writeShort(self.weight)
    - out.writeShort(self.port)
    - out.writeName(self.server)
    + def write(self, out):
    + """Used in constructing an outgoing packet"""
    + out.writeShort(self.priority)
    + out.writeShort(self.weight)
    + out.writeShort(self.port)
    + out.writeName(self.server)
    - def __eq__(self, other):
    - """Tests equality on priority, weight, port and server"""
    - if isinstance(other, DNSService):
    - return self.priority == other.priority and self.weight == other.weight and self.port == other.port and self.server == other.server
    - return 0
    + def __eq__(self, other):
    + """Tests equality on priority, weight, port and server"""
    + if isinstance(other, DNSService):
    + return self.priority == other.priority and self.weight == other.weight and self.port == other.port and self.server == other.server
    + return 0
    - def __repr__(self):
    - """String representation"""
    - return self.toString("%s:%s" % (self.server, self.port))
    + def __repr__(self):
    + """String representation"""
    + return self.toString("%s:%s" % (self.server, self.port))
    class DNSIncoming(object):
    - """Object representation of an incoming DNS packet"""
    -
    - def __init__(self, data):
    - """Constructor from string holding bytes of packet"""
    - self.offset = 0
    - self.data = data
    - self.questions = []
    - self.answers = []
    - self.numQuestions = 0
    - self.numAnswers = 0
    - self.numAuthorities = 0
    - self.numAdditionals = 0
    -
    - self.readHeader()
    - self.readQuestions()
    - self.readOthers()
    + """Object representation of an incoming DNS packet"""
    +
    + def __init__(self, data):
    + """Constructor from string holding bytes of packet"""
    + self.offset = 0
    + self.data = data
    + self.questions = []
    + self.answers = []
    + self.numQuestions = 0
    + self.numAnswers = 0
    + self.numAuthorities = 0
    + self.numAdditionals = 0
    +
    + self.readHeader()
    + self.readQuestions()
    + self.readOthers()
    - def readHeader(self):
    - """Reads header portion of packet"""
    - format = '!HHHHHH'
    - length = struct.calcsize(format)
    - info = struct.unpack(format, self.data[self.offset:self.offset+length])
    - self.offset += length
    + def readHeader(self):
    + """Reads header portion of packet"""
    + format = '!HHHHHH'
    + length = struct.calcsize(format)
    + info = struct.unpack(format, self.data[self.offset:self.offset+length])
    + self.offset += length
    - self.id = info[0]
    - self.flags = info[1]
    - self.numQuestions = info[2]
    - self.numAnswers = info[3]
    - self.numAuthorities = info[4]
    - self.numAdditionals = info[5]
    + self.id = info[0]
    + self.flags = info[1]
    + self.numQuestions = info[2]
    + self.numAnswers = info[3]
    + self.numAuthorities = info[4]
    + self.numAdditionals = info[5]
    - def readQuestions(self):
    - """Reads questions section of packet"""
    - format = '!HH'
    - length = struct.calcsize(format)
    - for i in range(0, self.numQuestions):
    - name = self.readName()
    - info = struct.unpack(format, self.data[self.offset:self.offset+length])
    - self.offset += length
    -
    - question = DNSQuestion(name, info[0], info[1])
    - self.questions.append(question)
    + def readQuestions(self):
    + """Reads questions section of packet"""
    + format = '!HH'
    + length = struct.calcsize(format)
    + for i in range(0, self.numQuestions):
    + name = self.readName()
    + info = struct.unpack(format, self.data[self.offset:self.offset+length])
    + self.offset += length
    +
    + question = DNSQuestion(name, info[0], info[1])
    + self.questions.append(question)
    - def readInt(self):
    - """Reads an integer from the packet"""
    - format = '!I'
    - length = struct.calcsize(format)
    - info = struct.unpack(format, self.data[self.offset:self.offset+length])
    - self.offset += length
    - return info[0]
    + def readInt(self):
    + """Reads an integer from the packet"""
    + format = '!I'
    + length = struct.calcsize(format)
    + info = struct.unpack(format, self.data[self.offset:self.offset+length])
    + self.offset += length
    + return info[0]
    - def readCharacterString(self):
    - """Reads a character string from the packet"""
    - length = ord(self.data[self.offset])
    - self.offset += 1
    - return self.readString(length)
    + def readCharacterString(self):
    + """Reads a character string from the packet"""
    + length = ord(self.data[self.offset])
    + self.offset += 1
    + return self.readString(length)
    - def readString(self, len):
    - """Reads a string of a given length from the packet"""
    - format = '!' + str(len) + 's'
    - length = struct.calcsize(format)
    - info = struct.unpack(format, self.data[self.offset:self.offset+length])
    - self.offset += length
    - return info[0]
    + def readString(self, len):
    + """Reads a string of a given length from the packet"""
    + format = '!' + str(len) + 's'
    + length = struct.calcsize(format)
    + info = struct.unpack(format, self.data[self.offset:self.offset+length])
    + self.offset += length
    + return info[0]
    - def readUnsignedShort(self):
    - """Reads an unsigned short from the packet"""
    - format = '!H'
    - length = struct.calcsize(format)
    - info = struct.unpack(format, self.data[self.offset:self.offset+length])
    - self.offset += length
    - return info[0]
    + def readUnsignedShort(self):
    + """Reads an unsigned short from the packet"""
    + format = '!H'
    + length = struct.calcsize(format)
    + info = struct.unpack(format, self.data[self.offset:self.offset+length])
    + self.offset += length
    + return info[0]
    - def readOthers(self):
    - """Reads the answers, authorities and additionals section of the packet"""
    - format = '!HHiH'
    - length = struct.calcsize(format)
    - n = self.numAnswers + self.numAuthorities + self.numAdditionals
    - for i in range(0, n):
    - domain = self.readName()
    - info = struct.unpack(format, self.data[self.offset:self.offset+length])
    - self.offset += length
    + def readOthers(self):
    + """Reads the answers, authorities and additionals section of the packet"""
    + format = '!HHiH'
    + length = struct.calcsize(format)
    + n = self.numAnswers + self.numAuthorities + self.numAdditionals
    + for i in range(0, n):
    + domain = self.readName()
    + info = struct.unpack(format, self.data[self.offset:self.offset+length])
    + self.offset += length
    - rec = None
    - if info[0] == _TYPE_A:
    - rec = DNSAddress(domain, info[0], info[1], info[2], self.readString(4))
    - elif info[0] == _TYPE_CNAME or info[0] == _TYPE_PTR:
    - rec = DNSPointer(domain, info[0], info[1], info[2], self.readName())
    - elif info[0] == _TYPE_TXT:
    - rec = DNSText(domain, info[0], info[1], info[2], self.readString(info[3]))
    - elif info[0] == _TYPE_SRV:
    - rec = DNSService(domain, info[0], info[1], info[2], self.readUnsignedShort(), self.readUnsignedShort(), self.readUnsignedShort(), self.readName())
    - elif info[0] == _TYPE_HINFO:
    - rec = DNSHinfo(domain, info[0], info[1], info[2], self.readCharacterString(), self.readCharacterString())
    - elif info[0] == _TYPE_AAAA:
    - rec = DNSAddress(domain, info[0], info[1], info[2], self.readString(16))
    - else:
    - # Try to ignore types we don't know about
    - # this may mean the rest of the name is
    - # unable to be parsed, and may show errors
    - # so this is left for debugging. New types
    - # encountered need to be parsed properly.
    - #
    - #print "UNKNOWN TYPE = " + str(info[0])
    - #raise BadTypeInNameException
    - pass
    + rec = None
    + if info[0] == _TYPE_A:
    + rec = DNSAddress(domain, info[0], info[1], info[2], self.readString(4))
    + elif info[0] == _TYPE_CNAME or info[0] == _TYPE_PTR:
    + rec = DNSPointer(domain, info[0], info[1], info[2], self.readName())
    + elif info[0] == _TYPE_TXT:
    + rec = DNSText(domain, info[0], info[1], info[2], self.readString(info[3]))
    + elif info[0] == _TYPE_SRV:
    + rec = DNSService(domain, info[0], info[1], info[2], self.readUnsignedShort(), self.readUnsignedShort(), self.readUnsignedShort(), self.readName())
    + elif info[0] == _TYPE_HINFO:
    + rec = DNSHinfo(domain, info[0], info[1], info[2], self.readCharacterString(), self.readCharacterString())
    + elif info[0] == _TYPE_AAAA:
    + rec = DNSAddress(domain, info[0], info[1], info[2], self.readString(16))
    + else:
    + # Try to ignore types we don't know about
    + # this may mean the rest of the name is
    + # unable to be parsed, and may show errors
    + # so this is left for debugging. New types
    + # encountered need to be parsed properly.
    + #
    + #print "UNKNOWN TYPE = " + str(info[0])
    + #raise BadTypeInNameException
    + pass
    - if rec is not None:
    - self.answers.append(rec)
    -
    - def isQuery(self):
    - """Returns true if this is a query"""
    - return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_QUERY
    + if rec is not None:
    + self.answers.append(rec)
    +
    + def isQuery(self):
    + """Returns true if this is a query"""
    + return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_QUERY
    - def isResponse(self):
    - """Returns true if this is a response"""
    - return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_RESPONSE
    + def isResponse(self):
    + """Returns true if this is a response"""
    + return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_RESPONSE
    - def readUTF(self, offset, len):
    - """Reads a UTF-8 string of a given length from the packet"""
    - result = self.data[offset:offset+len].decode('utf-8')
    - return result
    -
    - def readName(self):
    - """Reads a domain name from the packet"""
    - result = ''
    - off = self.offset
    - next = -1
    - first = off
    + def readUTF(self, offset, len):
    + """Reads a UTF-8 string of a given length from the packet"""
    + result = self.data[offset:offset+len].decode('utf-8')
    + return result
    +
    + def readName(self):
    + """Reads a domain name from the packet"""
    + result = ''
    + off = self.offset
    + next = -1
    + first = off
    - while 1:
    - len = ord(self.data[off])
    - off += 1
    - if len == 0:
    - break
    - t = len & 0xC0
    - if t == 0x00:
    - result = ''.join((result, self.readUTF(off, len) + '.'))
    - off += len
    - elif t == 0xC0:
    - if next < 0:
    - next = off + 1
    - off = ((len & 0x3F) << 8) | ord(self.data[off])
    - if off >= first:
    - raise "Bad domain name (circular) at " + str(off)
    - first = off
    - else:
    - raise "Bad domain name at " + str(off)
    + while 1:
    + len = ord(self.data[off])
    + off += 1
    + if len == 0:
    + break
    + t = len & 0xC0
    + if t == 0x00:
    + result = ''.join((result, self.readUTF(off, len) + '.'))
    + off += len
    + elif t == 0xC0:
    + if next < 0:
    + next = off + 1
    + off = ((len & 0x3F) << 8) | ord(self.data[off])
    + if off >= first:
    + raise "Bad domain name (circular) at " + str(off)
    + first = off
    + else:
    + raise "Bad domain name at " + str(off)
    - if next >= 0:
    - self.offset = next
    - else:
    - self.offset = off
    + if next >= 0:
    + self.offset = next
    + else:
    + self.offset = off
    - return result
    -
    -
    + return result
    +
    +
    class DNSOutgoing(object):
    - """Object representation of an outgoing packet"""
    -
    - def __init__(self, flags, multicast = 1):
    - self.finished = 0
    - self.id = 0
    - self.multicast = multicast
    - self.flags = flags
    - self.names = {}
    - self.data = []
    - self.size = 12
    -
    - self.questions = []
    - self.answers = []
    - self.authorities = []
    - self.additionals = []
    + """Object representation of an outgoing packet"""
    +
    + def __init__(self, flags, multicast = 1):
    + self.finished = 0
    + self.id = 0
    + self.multicast = multicast
    + self.flags = flags
    + self.names = {}
    + self.data = []
    + self.size = 12
    +
    + self.questions = []
    + self.answers = []
    + self.authorities = []
    + self.additionals = []
    - def addQuestion(self, record):
    - """Adds a question"""
    - self.questions.append(record)
    + def addQuestion(self, record):
    + """Adds a question"""
    + self.questions.append(record)
    - def addAnswer(self, inp, record):
    - """Adds an answer"""
    - if not record.suppressedBy(inp):
    - self.addAnswerAtTime(record, 0)
    + def addAnswer(self, inp, record):
    + """Adds an answer"""
    + if not record.suppressedBy(inp):
    + self.addAnswerAtTime(record, 0)
    - def addAnswerAtTime(self, record, now):
    - """Adds an answer if if does not expire by a certain time"""
    - if record is not None:
    - if now == 0 or not record.isExpired(now):
    - self.answers.append((record, now))
    + def addAnswerAtTime(self, record, now):
    + """Adds an answer if if does not expire by a certain time"""
    + if record is not None:
    + if now == 0 or not record.isExpired(now):
    + self.answers.append((record, now))
    - def addAuthorativeAnswer(self, record):
    - """Adds an authoritative answer"""
    - self.authorities.append(record)
    + def addAuthorativeAnswer(self, record):
    + """Adds an authoritative answer"""
    + self.authorities.append(record)
    - def addAdditionalAnswer(self, record):
    - """Adds an additional answer"""
    - self.additionals.append(record)
    + def addAdditionalAnswer(self, record):
    + """Adds an additional answer"""
    + self.additionals.append(record)
    - def writeByte(self, value):
    - """Writes a single byte to the packet"""
    - format = '!c'
    - self.data.append(struct.pack(format, chr(value)))
    - self.size += 1
    + def writeByte(self, value):
    + """Writes a single byte to the packet"""
    + format = '!c'
    + self.data.append(struct.pack(format, chr(value)))
    + self.size += 1
    - def insertShort(self, index, value):
    - """Inserts an unsigned short in a certain position in the packet"""
    - format = '!H'
    - self.data.insert(index, struct.pack(format, value))
    - self.size += 2
    -
    - def writeShort(self, value):
    - """Writes an unsigned short to the packet"""
    - format = '!H'
    - self.data.append(struct.pack(format, value))
    - self.size += 2
    + def insertShort(self, index, value):
    + """Inserts an unsigned short in a certain position in the packet"""
    + format = '!H'
    + self.data.insert(index, struct.pack(format, value))
    + self.size += 2
    +
    + def writeShort(self, value):
    + """Writes an unsigned short to the packet"""
    + format = '!H'
    + self.data.append(struct.pack(format, value))
    + self.size += 2
    - def writeInt(self, value):
    - """Writes an unsigned integer to the packet"""
    - format = '!I'
    - self.data.append(struct.pack(format, int(value)))
    - self.size += 4
    + def writeInt(self, value):
    + """Writes an unsigned integer to the packet"""
    + format = '!I'
    + self.data.append(struct.pack(format, int(value)))
    + self.size += 4
    - def writeString(self, value, length):
    - """Writes a string to the packet"""
    - format = '!' + str(length) + 's'
    - self.data.append(struct.pack(format, value))
    - self.size += length
    + def writeString(self, value, length):
    + """Writes a string to the packet"""
    + format = '!' + str(length) + 's'
    + self.data.append(struct.pack(format, value))
    + self.size += length
    - def writeUTF(self, s):
    - """Writes a UTF-8 string of a given length to the packet"""
    - utfstr = s.encode('utf-8')
    - length = len(utfstr)
    - if length > 64:
    - raise NamePartTooLongException
    - self.writeByte(length)
    - self.writeString(utfstr, length)
    + def writeUTF(self, s):
    + """Writes a UTF-8 string of a given length to the packet"""
    + utfstr = s.encode('utf-8')
    + length = len(utfstr)
    + if length > 64:
    + raise NamePartTooLongException
    + self.writeByte(length)
    + self.writeString(utfstr, length)
    - def writeName(self, name):
    - """Writes a domain name to the packet"""
    + def writeName(self, name):
    + """Writes a domain name to the packet"""
    - try:
    - # Find existing instance of this name in packet
    - #
    - index = self.names[name]
    - except KeyError:
    - # No record of this name already, so write it
    - # out as normal, recording the location of the name
    - # for future pointers to it.
    - #
    - self.names[name] = self.size
    - parts = name.split('.')
    - if parts[-1] == '':
    - parts = parts[:-1]
    - for part in parts:
    - self.writeUTF(part)
    - self.writeByte(0)
    - return
    + try:
    + # Find existing instance of this name in packet
    + #
    + index = self.names[name]
    + except KeyError:
    + # No record of this name already, so write it
    + # out as normal, recording the location of the name
    + # for future pointers to it.
    + #
    + self.names[name] = self.size
    + parts = name.split('.')
    + if parts[-1] == '':
    + parts = parts[:-1]
    + for part in parts:
    + self.writeUTF(part)
    + self.writeByte(0)
    + return
    - # An index was found, so write a pointer to it
    - #
    - self.writeByte((index >> 8) | 0xC0)
    - self.writeByte(index)
    + # An index was found, so write a pointer to it
    + #
    + self.writeByte((index >> 8) | 0xC0)
    + self.writeByte(index)
    - def writeQuestion(self, question):
    - """Writes a question to the packet"""
    - self.writeName(question.name)
    - self.writeShort(question.type)
    - self.writeShort(question.clazz)
    + def writeQuestion(self, question):
    + """Writes a question to the packet"""
    + self.writeName(question.name)
    + self.writeShort(question.type)
    + self.writeShort(question.clazz)
    - def writeRecord(self, record, now):
    - """Writes a record (answer, authoritative answer, additional) to
    - the packet"""
    - self.writeName(record.name)
    - self.writeShort(record.type)
    - if record.unique and self.multicast:
    - self.writeShort(record.clazz | _CLASS_UNIQUE)
    - else:
    - self.writeShort(record.clazz)
    - if now == 0:
    - self.writeInt(record.ttl)
    - else:
    - self.writeInt(record.getRemainingTTL(now))
    - index = len(self.data)
    - # Adjust size for the short we will write before this record
    - #
    - self.size += 2
    - record.write(self)
    - self.size -= 2
    -
    - length = len(''.join(self.data[index:]))
    - self.insertShort(index, length) # Here is the short we adjusted for
    + def writeRecord(self, record, now):
    + """Writes a record (answer, authoritative answer, additional) to
    + the packet"""
    + self.writeName(record.name)
    + self.writeShort(record.type)
    + if record.unique and self.multicast:
    + self.writeShort(record.clazz | _CLASS_UNIQUE)
    + else:
    + self.writeShort(record.clazz)
    + if now == 0:
    + self.writeInt(record.ttl)
    + else:
    + self.writeInt(record.getRemainingTTL(now))
    + index = len(self.data)
    + # Adjust size for the short we will write before this record
    + #
    + self.size += 2
    + record.write(self)
    + self.size -= 2
    +
    + length = len(''.join(self.data[index:]))
    + self.insertShort(index, length) # Here is the short we adjusted for
    - def packet(self):
    - """Returns a string containing the packet's bytes
    + def packet(self):
    + """Returns a string containing the packet's bytes
    - No further parts should be added to the packet once this
    - is done."""
    - if not self.finished:
    - self.finished = 1
    - for question in self.questions:
    - self.writeQuestion(question)
    - for answer, time in self.answers:
    - self.writeRecord(answer, time)
    - for authority in self.authorities:
    - self.writeRecord(authority, 0)
    - for additional in self.additionals:
    - self.writeRecord(additional, 0)
    -
    - self.insertShort(0, len(self.additionals))
    - self.insertShort(0, len(self.authorities))
    - self.insertShort(0, len(self.answers))
    - self.insertShort(0, len(self.questions))
    - self.insertShort(0, self.flags)
    - if self.multicast:
    - self.insertShort(0, 0)
    - else:
    - self.insertShort(0, self.id)
    - return ''.join(self.data)
    + No further parts should be added to the packet once this
    + is done."""
    + if not self.finished:
    + self.finished = 1
    + for question in self.questions:
    + self.writeQuestion(question)
    + for answer, time in self.answers:
    + self.writeRecord(answer, time)
    + for authority in self.authorities:
    + self.writeRecord(authority, 0)
    + for additional in self.additionals:
    + self.writeRecord(additional, 0)
    +
    + self.insertShort(0, len(self.additionals))
    + self.insertShort(0, len(self.authorities))
    + self.insertShort(0, len(self.answers))
    + self.insertShort(0, len(self.questions))
    + self.insertShort(0, self.flags)
    + if self.multicast:
    + self.insertShort(0, 0)
    + else:
    + self.insertShort(0, self.id)
    + return ''.join(self.data)
    class DNSCache(object):
    - """A cache of DNS entries"""
    -
    - def __init__(self):
    - self.cache = {}
    + """A cache of DNS entries"""
    +
    + def __init__(self):
    + self.cache = {}
    - def add(self, entry):
    - """Adds an entry"""
    - try:
    - list = self.cache[entry.key]
    - except:
    - list = self.cache[entry.key] = []
    - list.append(entry)
    + def add(self, entry):
    + """Adds an entry"""
    + try:
    + list = self.cache[entry.key]
    + except:
    + list = self.cache[entry.key] = []
    + list.append(entry)
    - def remove(self, entry):
    - """Removes an entry"""
    - try:
    - list = self.cache[entry.key]
    - list.remove(entry)
    - except:
    - pass
    + def remove(self, entry):
    + """Removes an entry"""
    + try:
    + list = self.cache[entry.key]
    + list.remove(entry)
    + except:
    + pass
    - def get(self, entry):
    - """Gets an entry by key. Will return None if there is no
    - matching entry."""
    - try:
    - list = self.cache[entry.key]
    - return list[list.index(entry)]
    - except:
    - return None
    + def get(self, entry):
    + """Gets an entry by key. Will return None if there is no
    + matching entry."""
    + try:
    + list = self.cache[entry.key]
    + return list[list.index(entry)]
    + except:
    + return None
    - def getByDetails(self, name, type, clazz):
    - """Gets an entry by details. Will return None if there is
    - no matching entry."""
    - entry = DNSEntry(name, type, clazz)
    - return self.get(entry)
    + def getByDetails(self, name, type, clazz):
    + """Gets an entry by details. Will return None if there is
    + no matching entry."""
    + entry = DNSEntry(name, type, clazz)
    + return self.get(entry)
    - def entriesWithName(self, name):
    - """Returns a list of entries whose key matches the name."""
    - try:
    - return self.cache[name]
    - except:
    - return []
    + def entriesWithName(self, name):
    + """Returns a list of entries whose key matches the name."""
    + try:
    + return self.cache[name]
    + except:
    + return []
    - def entries(self):
    - """Returns a list of all entries"""
    - def add(x, y): return x+y
    - try:
    - return reduce(add, self.cache.values())
    - except:
    - return []
    + def entries(self):
    + """Returns a list of all entries"""
    + def add(x, y): return x+y
    + try:
    + return reduce(add, self.cache.values())
    + except:
    + return []
    class Engine(threading.Thread):
    - """An engine wraps read access to sockets, allowing objects that
    - need to receive data from sockets to be called back when the
    - sockets are ready.
    + """An engine wraps read access to sockets, allowing objects that
    + need to receive data from sockets to be called back when the
    + sockets are ready.
    - A reader needs a handle_read() method, which is called when the socket
    - it is interested in is ready for reading.
    + A reader needs a handle_read() method, which is called when the socket
    + it is interested in is ready for reading.
    - Writers are not implemented here, because we only send short
    - packets.
    - """
    + Writers are not implemented here, because we only send short
    + packets.
    + """
    - def __init__(self, zeroconf):
    - threading.Thread.__init__(self)
    - self.zeroconf = zeroconf
    - self.readers = {} # maps socket to reader
    - self.timeout = 5
    - self.condition = threading.Condition()
    - self.start()
    + def __init__(self, zeroconf):
    + threading.Thread.__init__(self)
    + self.zeroconf = zeroconf
    + self.readers = {} # maps socket to reader
    + self.timeout = 5
    + self.condition = threading.Condition()
    + self.start()
    - def run(self):
    - while not globals()['_GLOBAL_DONE']:
    - rs = self.getReaders()
    - if len(rs) == 0:
    - # No sockets to manage, but we wait for the timeout
    - # or addition of a socket
    - #
    - self.condition.acquire()
    - self.condition.wait(self.timeout)
    - self.condition.release()
    - else:
    - try:
    - rr, wr, er = select.select(rs, [], [], self.timeout)
    - for socket in rr:
    - try:
    - self.readers[socket].handle_read()
    - except:
    - traceback.print_exc()
    - except:
    - pass
    + def run(self):
    + while not globals()['_GLOBAL_DONE']:
    + rs = self.getReaders()
    + if len(rs) == 0:
    + # No sockets to manage, but we wait for the timeout
    + # or addition of a socket
    + #
    + self.condition.acquire()
    + self.condition.wait(self.timeout)
    + self.condition.release()
    + else:
    + try:
    + rr, wr, er = select.select(rs, [], [], self.timeout)
    + for socket in rr:
    + try:
    + self.readers[socket].handle_read()
    + except:
    + traceback.print_exc()
    + except:
    + pass
    - def getReaders(self):
    - result = []
    - self.condition.acquire()
    - result = self.readers.keys()
    - self.condition.release()
    - return result
    -
    - def addReader(self, reader, socket):
    - self.condition.acquire()
    - self.readers[socket] = reader
    - self.condition.notify()
    - self.condition.release()
    + def getReaders(self):
    + result = []
    + self.condition.acquire()
    + result = self.readers.keys()
    + self.condition.release()
    + return result
    +
    + def addReader(self, reader, socket):
    + self.condition.acquire()
    + self.readers[socket] = reader
    + self.condition.notify()
    + self.condition.release()
    - def delReader(self, socket):
    - self.condition.acquire()
    - del(self.readers[socket])
    - self.condition.notify()
    - self.condition.release()
    + def delReader(self, socket):
    + self.condition.acquire()
    + del(self.readers[socket])
    + self.condition.notify()
    + self.condition.release()
    - def notify(self):
    - self.condition.acquire()
    - self.condition.notify()
    - self.condition.release()
    + def notify(self):
    + self.condition.acquire()
    + self.condition.notify()
    + self.condition.release()
    class Listener(object):
    - """A Listener is used by this module to listen on the multicast
    - group to which DNS messages are sent, allowing the implementation
    - to cache information as it arrives.
    + """A Listener is used by this module to listen on the multicast
    + group to which DNS messages are sent, allowing the implementation
    + to cache information as it arrives.
    - It requires registration with an Engine object in order to have
    - the read() method called when a socket is availble for reading."""
    -
    - def __init__(self, zeroconf):
    - self.zeroconf = zeroconf
    - self.zeroconf.engine.addReader(self, self.zeroconf.socket)
    + It requires registration with an Engine object in order to have
    + the read() method called when a socket is availble for reading."""
    +
    + def __init__(self, zeroconf):
    + self.zeroconf = zeroconf
    + self.zeroconf.engine.addReader(self, self.zeroconf.socket)
    - def handle_read(self):
    - data, (addr, port) = self.zeroconf.socket.recvfrom(_MAX_MSG_ABSOLUTE)
    - self.data = data
    - msg = DNSIncoming(data)
    - if msg.isQuery():
    - # Always multicast responses
    - #
    - if port == _MDNS_PORT:
    - self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT)
    - # If it's not a multicast query, reply via unicast
    - # and multicast
    - #
    - elif port == _DNS_PORT:
    - self.zeroconf.handleQuery(msg, addr, port)
    - self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT)
    - else:
    - self.zeroconf.handleResponse(msg)
    + def handle_read(self):
    + data, (addr, port) = self.zeroconf.socket.recvfrom(_MAX_MSG_ABSOLUTE)
    + self.data = data
    + msg = DNSIncoming(data)
    + if msg.isQuery():
    + # Always multicast responses
    + #
    + if port == _MDNS_PORT:
    + self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT)
    + # If it's not a multicast query, reply via unicast
    + # and multicast
    + #
    + elif port == _DNS_PORT:
    + self.zeroconf.handleQuery(msg, addr, port)
    + self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT)
    + else:
    + self.zeroconf.handleResponse(msg)
    class Reaper(threading.Thread):
    - """A Reaper is used by this module to remove cache entries that
    - have expired."""
    -
    - def __init__(self, zeroconf):
    - threading.Thread.__init__(self)
    - self.zeroconf = zeroconf
    - self.start()
    + """A Reaper is used by this module to remove cache entries that
    + have expired."""
    +
    + def __init__(self, zeroconf):
    + threading.Thread.__init__(self)
    + self.zeroconf = zeroconf
    + self.start()
    - def run(self):
    - while 1:
    - self.zeroconf.wait(10 * 1000)
    - if globals()['_GLOBAL_DONE']:
    - return
    - now = currentTimeMillis()
    - for record in self.zeroconf.cache.entries():
    - if record.isExpired(now):
    - self.zeroconf.updateRecord(now, record)
    - self.zeroconf.cache.remove(record)
    + def run(self):
    + while 1:
    + self.zeroconf.wait(10 * 1000)
    + if globals()['_GLOBAL_DONE']:
    + return
    + now = currentTimeMillis()
    + for record in self.zeroconf.cache.entries():
    + if record.isExpired(now):
    + self.zeroconf.updateRecord(now, record)
    + self.zeroconf.cache.remove(record)
    class ServiceBrowser(threading.Thread):
    - """Used to browse for a service of a specific type.
    + """Used to browse for a service of a specific type.
    - The listener object will have its addService() and
    - removeService() methods called when this browser
    - discovers changes in the services availability."""
    -
    - def __init__(self, zeroconf, type, listener):
    - """Creates a browser for a specific type"""
    - threading.Thread.__init__(self)
    - self.zeroconf = zeroconf
    - self.type = type
    - self.listener = listener
    - self.services = {}
    - self.nextTime = currentTimeMillis()
    - self.delay = _BROWSER_TIME
    - self.list = []
    -
    - self.done = 0
    + The listener object will have its addService() and
    + removeService() methods called when this browser
    + discovers changes in the services availability."""
    +
    + def __init__(self, zeroconf, type, listener):
    + """Creates a browser for a specific type"""
    + threading.Thread.__init__(self)
    + self.zeroconf = zeroconf
    + self.type = type
    + self.listener = listener
    + self.services = {}
    + self.nextTime = currentTimeMillis()
    + self.delay = _BROWSER_TIME
    + self.list = []
    +
    + self.done = 0
    - self.zeroconf.addListener(self, DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN))
    - self.start()
    + self.zeroconf.addListener(self, DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN))
    + self.start()
    - def updateRecord(self, zeroconf, now, record):
    - """Callback invoked by Zeroconf when new information arrives.
    + def updateRecord(self, zeroconf, now, record):
    + """Callback invoked by Zeroconf when new information arrives.
    - Updates information required by browser in the Zeroconf cache."""
    - if record.type == _TYPE_PTR and record.name == self.type:
    - expired = record.isExpired(now)
    - try:
    - oldrecord = self.services[record.alias.lower()]
    - if not expired:
    - oldrecord.resetTTL(record)
    - else:
    - del(self.services[record.alias.lower()])
    - callback = lambda x: self.listener.removeService(x, self.type, record.alias)
    - self.list.append(callback)
    - return
    - except:
    - if not expired:
    - self.services[record.alias.lower()] = record
    - callback = lambda x: self.listener.addService(x, self.type, record.alias)
    - self.list.append(callback)
    + Updates information required by browser in the Zeroconf cache."""
    + if record.type == _TYPE_PTR and record.name == self.type:
    + expired = record.isExpired(now)
    + try:
    + oldrecord = self.services[record.alias.lower()]
    + if not expired:
    + oldrecord.resetTTL(record)
    + else:
    + del(self.services[record.alias.lower()])
    + callback = lambda x: self.listener.removeService(x, self.type, record.alias)
    + self.list.append(callback)
    + return
    + except:
    + if not expired:
    + self.services[record.alias.lower()] = record
    + callback = lambda x: self.listener.addService(x, self.type, record.alias)
    + self.list.append(callback)
    - expires = record.getExpirationTime(75)
    - if expires < self.nextTime:
    - self.nextTime = expires
    + expires = record.getExpirationTime(75)
    + if expires < self.nextTime:
    + self.nextTime = expires
    - def cancel(self):
    - self.done = 1
    - self.zeroconf.notifyAll()
    + def cancel(self):
    + self.done = 1
    + self.zeroconf.notifyAll()
    - def run(self):
    - while 1:
    - event = None
    - now = currentTimeMillis()
    - if len(self.list) == 0 and self.nextTime > now:
    - self.zeroconf.wait(self.nextTime - now)
    - if globals()['_GLOBAL_DONE'] or self.done:
    - return
    - now = currentTimeMillis()
    + def run(self):
    + while 1:
    + event = None
    + now = currentTimeMillis()
    + if len(self.list) == 0 and self.nextTime > now:
    + self.zeroconf.wait(self.nextTime - now)
    + if globals()['_GLOBAL_DONE'] or self.done:
    + return
    + now = currentTimeMillis()
    - if self.nextTime <= now:
    - out = DNSOutgoing(_FLAGS_QR_QUERY)
    - out.addQuestion(DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN))
    - for record in self.services.values():
    - if not record.isExpired(now):
    - out.addAnswerAtTime(record, now)
    - self.zeroconf.send(out)
    - self.nextTime = now + self.delay
    - self.delay = min(20 * 1000, self.delay * 2)
    + if self.nextTime <= now:
    + out = DNSOutgoing(_FLAGS_QR_QUERY)
    + out.addQuestion(DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN))
    + for record in self.services.values():
    + if not record.isExpired(now):
    + out.addAnswerAtTime(record, now)
    + self.zeroconf.send(out)
    + self.nextTime = now + self.delay
    + self.delay = min(20 * 1000, self.delay * 2)
    - if len(self.list) > 0:
    - event = self.list.pop(0)
    + if len(self.list) > 0:
    + event = self.list.pop(0)
    - if event is not None:
    - event(self.zeroconf)
    -
    + if event is not None:
    + event(self.zeroconf)
    +
    class ServiceInfo(object):
    - """Service information"""
    -
    - def __init__(self, type, name, address=None, port=None, weight=0, priority=0, properties=None, server=None):
    - """Create a service description.
    + """Service information"""
    +
    + def __init__(self, type, name, address=None, port=None, weight=0, priority=0, properties=None, server=None):
    + """Create a service description.
    - type: fully qualified service type name
    - name: fully qualified service name
    - address: IP address as unsigned short, network byte order
    - port: port that the service runs on
    - weight: weight of the service
    - priority: priority of the service
    - properties: dictionary of properties (or a string holding the bytes for the text field)
    - server: fully qualified name for service host (defaults to name)"""
    + type: fully qualified service type name
    + name: fully qualified service name
    + address: IP address as unsigned short, network byte order
    + port: port that the service runs on
    + weight: weight of the service
    + priority: priority of the service
    + properties: dictionary of properties (or a string holding the bytes for the text field)
    + server: fully qualified name for service host (defaults to name)"""
    - if not name.endswith(type):
    - raise BadTypeInNameException
    - self.type = type
    - self.name = name
    - self.address = address
    - self.port = port
    - self.weight = weight
    - self.priority = priority
    - if server:
    - self.server = server
    - else:
    - self.server = name
    - self.setProperties(properties)
    + if not name.endswith(type):
    + raise BadTypeInNameException
    + self.type = type
    + self.name = name
    + self.address = address
    + self.port = port
    + self.weight = weight
    + self.priority = priority
    + if server:
    + self.server = server
    + else:
    + self.server = name
    + self.setProperties(properties)
    - def setProperties(self, properties):
    - """Sets properties and text of this info from a dictionary"""
    - if isinstance(properties, dict):
    - self.properties = properties
    - list = []
    - result = ''
    - for key in properties:
    - value = properties[key]
    - if value is None:
    - suffix = ''.encode('utf-8')
    - elif isinstance(value, str):
    - suffix = value.encode('utf-8')
    - elif isinstance(value, int):
    - if value:
    - suffix = 'true'
    - else:
    - suffix = 'false'
    - else:
    - suffix = ''.encode('utf-8')
    - list.append('='.join((key, suffix)))
    - for item in list:
    - result = ''.join((result, struct.pack('!c', chr(len(item))), item))
    - self.text = result
    - else:
    - self.text = properties
    + def setProperties(self, properties):
    + """Sets properties and text of this info from a dictionary"""
    + if isinstance(properties, dict):
    + self.properties = properties
    + list = []
    + result = ''
    + for key in properties:
    + value = properties[key]
    + if value is None:
    + suffix = ''.encode('utf-8')
    + elif isinstance(value, str):
    + suffix = value.encode('utf-8')
    + elif isinstance(value, int):
    + if value:
    + suffix = 'true'
    + else:
    + suffix = 'false'
    + else:
    + suffix = ''.encode('utf-8')
    + list.append('='.join((key, suffix)))
    + for item in list:
    + result = ''.join((result, struct.pack('!c', chr(len(item))), item))
    + self.text = result
    + else:
    + self.text = properties
    - def setText(self, text):
    - """Sets properties and text given a text field"""
    - self.text = text
    - try:
    - result = {}
    - end = len(text)
    - index = 0
    - strs = []
    - while index < end:
    - length = ord(text[index])
    - index += 1
    - strs.append(text[index:index+length])
    - index += length
    -
    - for s in strs:
    - eindex = s.find('=')
    - if eindex == -1:
    - # No equals sign at all
    - key = s
    - value = 0
    - else:
    - key = s[:eindex]
    - value = s[eindex+1:]
    - if value == 'true':
    - value = 1
    - elif value == 'false' or not value:
    - value = 0
    + def setText(self, text):
    + """Sets properties and text given a text field"""
    + self.text = text
    + try:
    + result = {}
    + end = len(text)
    + index = 0
    + strs = []
    + while index < end:
    + length = ord(text[index])
    + index += 1
    + strs.append(text[index:index+length])
    + index += length
    +
    + for s in strs:
    + eindex = s.find('=')
    + if eindex == -1:
    + # No equals sign at all
    + key = s
    + value = 0
    + else:
    + key = s[:eindex]
    + value = s[eindex+1:]
    + if value == 'true':
    + value = 1
    + elif value == 'false' or not value:
    + value = 0
    - # Only update non-existent properties
    - if key and result.get(key) == None:
    - result[key] = value
    + # Only update non-existent properties
    + if key and result.get(key) == None:
    + result[key] = value
    - self.properties = result
    - except:
    - traceback.print_exc()
    - self.properties = None
    -
    - def getType(self):
    - """Type accessor"""
    - return self.type
    + self.properties = result
    + except:
    + traceback.print_exc()
    + self.properties = None
    +
    + def getType(self):
    + """Type accessor"""
    + return self.type
    - def getName(self):
    - """Name accessor"""
    - if self.type is not None and self.name.endswith("." + self.type):
    - return self.name[:len(self.name) - len(self.type) - 1]
    - return self.name
    + def getName(self):
    + """Name accessor"""
    + if self.type is not None and self.name.endswith("." + self.type):
    + return self.name[:len(self.name) - len(self.type) - 1]
    + return self.name
    - def getAddress(self):
    - """Address accessor"""
    - return self.address
    + def getAddress(self):
    + """Address accessor"""
    + return self.address
    - def getPort(self):
    - """Port accessor"""
    - return self.port
    + def getPort(self):
    + """Port accessor"""
    + return self.port
    - def getPriority(self):
    - """Pirority accessor"""
    - return self.priority
    + def getPriority(self):
    + """Pirority accessor"""
    + return self.priority
    - def getWeight(self):
    - """Weight accessor"""
    - return self.weight
    + def getWeight(self):
    + """Weight accessor"""
    + return self.weight
    - def getProperties(self):
    - """Properties accessor"""
    - return self.properties
    + def getProperties(self):
    + """Properties accessor"""
    + return self.properties
    - def getText(self):
    - """Text accessor"""
    - return self.text
    + def getText(self):
    + """Text accessor"""
    + return self.text
    - def getServer(self):
    - """Server accessor"""
    - return self.server
    + def getServer(self):
    + """Server accessor"""
    + return self.server
    - def updateRecord(self, zeroconf, now, record):
    - """Updates service information from a DNS record"""
    - if record is not None and not record.isExpired(now):
    - if record.type == _TYPE_A:
    - if record.name == self.name:
    - self.address = record.address
    - elif record.type == _TYPE_SRV:
    - if record.name == self.name:
    - self.server = record.server
    - self.port = record.port
    - self.weight = record.weight
    - self.priority = record.priority
    - self.address = None
    - self.updateRecord(zeroconf, now, zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN))
    - elif record.type == _TYPE_TXT:
    - if record.name == self.name:
    - self.setText(record.text)
    + def updateRecord(self, zeroconf, now, record):
    + """Updates service information from a DNS record"""
    + if record is not None and not record.isExpired(now):
    + if record.type == _TYPE_A:
    + if record.name == self.name:
    + self.address = record.address
    + elif record.type == _TYPE_SRV:
    + if record.name == self.name:
    + self.server = record.server
    + self.port = record.port
    + self.weight = record.weight
    + self.priority = record.priority
    + self.address = None
    + self.updateRecord(zeroconf, now, zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN))
    + elif record.type == _TYPE_TXT:
    + if record.name == self.name:
    + self.setText(record.text)
    - def request(self, zeroconf, timeout):
    - """Returns true if the service could be discovered on the
    - network, and updates this object with details discovered.
    - """
    - now = currentTimeMillis()
    - delay = _LISTENER_TIME
    - next = now + delay
    - last = now + timeout
    - result = 0
    - try:
    - zeroconf.addListener(self, DNSQuestion(self.name, _TYPE_ANY, _CLASS_IN))
    - while self.server is None or self.address is None or self.text is None:
    - if last <= now:
    - return 0
    - if next <= now:
    - out = DNSOutgoing(_FLAGS_QR_QUERY)
    - out.addQuestion(DNSQuestion(self.name, _TYPE_SRV, _CLASS_IN))
    - out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_SRV, _CLASS_IN), now)
    - out.addQuestion(DNSQuestion(self.name, _TYPE_TXT, _CLASS_IN))
    - out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_TXT, _CLASS_IN), now)
    - if self.server is not None:
    - out.addQuestion(DNSQuestion(self.server, _TYPE_A, _CLASS_IN))
    - out.addAnswerAtTime(zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN), now)
    - zeroconf.send(out)
    - next = now + delay
    - delay = delay * 2
    + def request(self, zeroconf, timeout):
    + """Returns true if the service could be discovered on the
    + network, and updates this object with details discovered.
    + """
    + now = currentTimeMillis()
    + delay = _LISTENER_TIME
    + next = now + delay
    + last = now + timeout
    + result = 0
    + try:
    + zeroconf.addListener(self, DNSQuestion(self.name, _TYPE_ANY, _CLASS_IN))
    + while self.server is None or self.address is None or self.text is None:
    + if last <= now:
    + return 0
    + if next <= now:
    + out = DNSOutgoing(_FLAGS_QR_QUERY)
    + out.addQuestion(DNSQuestion(self.name, _TYPE_SRV, _CLASS_IN))
    + out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_SRV, _CLASS_IN), now)
    + out.addQuestion(DNSQuestion(self.name, _TYPE_TXT, _CLASS_IN))
    + out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_TXT, _CLASS_IN), now)
    + if self.server is not None:
    + out.addQuestion(DNSQuestion(self.server, _TYPE_A, _CLASS_IN))
    + out.addAnswerAtTime(zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN), now)
    + zeroconf.send(out)
    + next = now + delay
    + delay = delay * 2
    - zeroconf.wait(min(next, last) - now)
    - now = currentTimeMillis()
    - result = 1
    - finally:
    - zeroconf.removeListener(self)
    -
    - return result
    + zeroconf.wait(min(next, last) - now)
    + now = currentTimeMillis()
    + result = 1
    + finally:
    + zeroconf.removeListener(self)
    +
    + return result
    - def __eq__(self, other):
    - """Tests equality of service name"""
    - if isinstance(other, ServiceInfo):
    - return other.name == self.name
    - return 0
    + def __eq__(self, other):
    + """Tests equality of service name"""
    + if isinstance(other, ServiceInfo):
    + return other.name == self.name
    + return 0
    - def __ne__(self, other):
    - """Non-equality test"""
    - return not self.__eq__(other)
    + def __ne__(self, other):
    + """Non-equality test"""
    + return not self.__eq__(other)
    - def __repr__(self):
    - """String representation"""
    - result = "service[%s,%s:%s," % (self.name, socket.inet_ntoa(self.getAddress()), self.port)
    - if self.text is None:
    - result += "None"
    - else:
    - if len(self.text) < 20:
    - result += self.text
    - else:
    - result += self.text[:17] + "..."
    - result += "]"
    - return result
    -
    + def __repr__(self):
    + """String representation"""
    + result = "service[%s,%s:%s," % (self.name, socket.inet_ntoa(self.getAddress()), self.port)
    + if self.text is None:
    + result += "None"
    + else:
    + if len(self.text) < 20:
    + result += self.text
    + else:
    + result += self.text[:17] + "..."
    + result += "]"
    + return result
    +
    class Zeroconf(object):
    - """Implementation of Zeroconf Multicast DNS Service Discovery
    + """Implementation of Zeroconf Multicast DNS Service Discovery
    - Supports registration, unregistration, queries and browsing.
    - """
    - def __init__(self, bindaddress=None):
    - """Creates an instance of the Zeroconf class, establishing
    - multicast communications, listening and reaping threads."""
    - globals()['_GLOBAL_DONE'] = 0
    - if bindaddress is None:
    - self.intf = socket.gethostbyname(socket.gethostname())
    - else:
    - self.intf = bindaddress
    - self.group = ('', _MDNS_PORT)
    - self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    - try:
    - self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    - self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
    - except:
    - # SO_REUSEADDR should be equivalent to SO_REUSEPORT for
    - # multicast UDP sockets (p 731, "TCP/IP Illustrated,
    - # Volume 2"), but some BSD-derived systems require
    - # SO_REUSEPORT to be specified explicity. Also, not all
    - # versions of Python have SO_REUSEPORT available. So
    - # if you're on a BSD-based system, and haven't upgraded
    - # to Python 2.3 yet, you may find this library doesn't
    - # work as expected.
    - #
    - pass
    - self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, 255)
    - self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
    - try:
    - self.socket.bind(self.group)
    - except:
    - # Some versions of linux raise an exception even though
    - # the SO_REUSE* options have been set, so ignore it
    - #
    - pass
    - self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_IF, socket.inet_aton(self.intf) + socket.inet_aton('0.0.0.0'))
    - self.socket.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0'))
    + Supports registration, unregistration, queries and browsing.
    + """
    + def __init__(self, bindaddress=None):
    + """Creates an instance of the Zeroconf class, establishing
    + multicast communications, listening and reaping threads."""
    + globals()['_GLOBAL_DONE'] = 0
    + self.intf = bindaddress
    + self.group = ('', _MDNS_PORT)
    + self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    + try:
    + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
    + except:
    + # SO_REUSEADDR should be equivalent to SO_REUSEPORT for
    + # multicast UDP sockets (p 731, "TCP/IP Illustrated,
    + # Volume 2"), but some BSD-derived systems require
    + # SO_REUSEPORT to be specified explicity. Also, not all
    + # versions of Python have SO_REUSEPORT available. So
    + # if you're on a BSD-based system, and haven't upgraded
    + # to Python 2.3 yet, you may find this library doesn't
    + # work as expected.
    + #
    + pass
    + self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, 255)
    + self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
    + try:
    + self.socket.bind(self.group)
    + except:
    + # Some versions of linux raise an exception even though
    + # the SO_REUSE* options have been set, so ignore it
    + #
    + pass
    - self.listeners = []
    - self.browsers = []
    - self.services = {}
    + if self.intf is not None:
    + self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_IF, socket.inet_aton(self.intf) + socket.inet_aton('0.0.0.0'))
    + self.socket.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0'))
    - self.cache = DNSCache()
    + self.listeners = []
    + self.browsers = []
    + self.services = {}
    - self.condition = threading.Condition()
    -
    - self.engine = Engine(self)
    - self.listener = Listener(self)
    - self.reaper = Reaper(self)
    + self.cache = DNSCache()
    - def isLoopback(self):
    - return self.intf.startswith("127.0.0.1")
    + self.condition = threading.Condition()
    +
    + self.engine = Engine(self)
    + self.listener = Listener(self)
    + self.reaper = Reaper(self)
    - def isLinklocal(self):
    - return self.intf.startswith("169.254.")
    + def isLoopback(self):
    + if self.intf is not None:
    + return self.intf.startswith("127.0.0.1")
    + return False
    - def wait(self, timeout):
    - """Calling thread waits for a given number of milliseconds or
    - until notified."""
    - self.condition.acquire()
    - self.condition.wait(timeout/1000)
    - self.condition.release()
    + def isLinklocal(self):
    + if self.intf is not None:
    + return self.intf.startswith("169.254.")
    + return False
    - def notifyAll(self):
    - """Notifies all waiting threads"""
    - self.condition.acquire()
    - self.condition.notifyAll()
    - self.condition.release()
    + def wait(self, timeout):
    + """Calling thread waits for a given number of milliseconds or
    + until notified."""
    + self.condition.acquire()
    + self.condition.wait(timeout/1000)
    + self.condition.release()
    - def getServiceInfo(self, type, name, timeout=3000):
    - """Returns network's service information for a particular
    - name and type, or None if no service matches by the timeout,
    - which defaults to 3 seconds."""
    - info = ServiceInfo(type, name)
    - if info.request(self, timeout):
    - return info
    - return None
    + def notifyAll(self):
    + """Notifies all waiting threads"""
    + self.condition.acquire()
    + self.condition.notifyAll()
    + self.condition.release()
    - def addServiceListener(self, type, listener):
    - """Adds a listener for a particular service type. This object
    - will then have its updateRecord method called when information
    - arrives for that type."""
    - self.removeServiceListener(listener)
    - self.browsers.append(ServiceBrowser(self, type, listener))
    + def getServiceInfo(self, type, name, timeout=3000):
    + """Returns network's service information for a particular
    + name and type, or None if no service matches by the timeout,
    + which defaults to 3 seconds."""
    + info = ServiceInfo(type, name)
    + if info.request(self, timeout):
    + return info
    + return None
    - def removeServiceListener(self, listener):
    - """Removes a listener from the set that is currently listening."""
    - for browser in self.browsers:
    - if browser.listener == listener:
    - browser.cancel()
    - del(browser)
    + def addServiceListener(self, type, listener):
    + """Adds a listener for a particular service type. This object
    + will then have its updateRecord method called when information
    + arrives for that type."""
    + self.removeServiceListener(listener)
    + self.browsers.append(ServiceBrowser(self, type, listener))
    +
    + def removeServiceListener(self, listener):
    + """Removes a listener from the set that is currently listening."""
    + for browser in self.browsers:
    + if browser.listener == listener:
    + browser.cancel()
    + del(browser)
    - def registerService(self, info, ttl=_DNS_TTL):
    - """Registers service information to the network with a default TTL
    - of 60 seconds. Zeroconf will then respond to requests for
    - information for that service. The name of the service may be
    - changed if needed to make it unique on the network."""
    - self.checkService(info)
    - self.services[info.name.lower()] = info
    - now = currentTimeMillis()
    - nextTime = now
    - i = 0
    - while i < 3:
    - if now < nextTime:
    - self.wait(nextTime - now)
    - now = currentTimeMillis()
    - continue
    - out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
    - out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, ttl, info.name), 0)
    - out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, ttl, info.priority, info.weight, info.port, info.server), 0)
    - out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, ttl, info.text), 0)
    - if info.address:
    - out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, ttl, info.address), 0)
    - self.send(out)
    - i += 1
    - nextTime += _REGISTER_TIME
    + def registerService(self, info, ttl=_DNS_TTL):
    + """Registers service information to the network with a default TTL
    + of 60 seconds. Zeroconf will then respond to requests for
    + information for that service. The name of the service may be
    + changed if needed to make it unique on the network."""
    + self.checkService(info)
    + self.services[info.name.lower()] = info
    + now = currentTimeMillis()
    + nextTime = now
    + i = 0
    + while i < 3:
    + if now < nextTime:
    + self.wait(nextTime - now)
    + now = currentTimeMillis()
    + continue
    + out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
    + out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, ttl, info.name), 0)
    + out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, ttl, info.priority, info.weight, info.port, info.server), 0)
    + out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, ttl, info.text), 0)
    + if info.address:
    + out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, ttl, info.address), 0)
    + self.send(out)
    + i += 1
    + nextTime += _REGISTER_TIME
    - def unregisterService(self, info):
    - """Unregister a service."""
    - try:
    - del(self.services[info.name.lower()])
    - except:
    - pass
    - now = currentTimeMillis()
    - nextTime = now
    - i = 0
    - while i < 3:
    - if now < nextTime:
    - self.wait(nextTime - now)
    - now = currentTimeMillis()
    - continue
    - out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
    - out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0)
    - out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.name), 0)
    - out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0)
    - if info.address:
    - out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0)
    - self.send(out)
    - i += 1
    - nextTime += _UNREGISTER_TIME
    + def unregisterService(self, info):
    + """Unregister a service."""
    + try:
    + del(self.services[info.name.lower()])
    + except:
    + pass
    + now = currentTimeMillis()
    + nextTime = now
    + i = 0
    + while i < 3:
    + if now < nextTime:
    + self.wait(nextTime - now)
    + now = currentTimeMillis()
    + continue
    + out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
    + out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0)
    + out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.name), 0)
    + out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0)
    + if info.address:
    + out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0)
    + self.send(out)
    + i += 1
    + nextTime += _UNREGISTER_TIME
    - def unregisterAllServices(self):
    - """Unregister all registered services."""
    - if len(self.services) > 0:
    - now = currentTimeMillis()
    - nextTime = now
    - i = 0
    - while i < 3:
    - if now < nextTime:
    - self.wait(nextTime - now)
    - now = currentTimeMillis()
    - continue
    - out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
    - for info in self.services.values():
    - out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0)
    - out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.server), 0)
    - out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0)
    - if info.address:
    - out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0)
    - self.send(out)
    - i += 1
    - nextTime += _UNREGISTER_TIME
    + def unregisterAllServices(self):
    + """Unregister all registered services."""
    + if len(self.services) > 0:
    + now = currentTimeMillis()
    + nextTime = now
    + i = 0
    + while i < 3:
    + if now < nextTime:
    + self.wait(nextTime - now)
    + now = currentTimeMillis()
    + continue
    + out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
    + for info in self.services.values():
    + out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0)
    + out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.server), 0)
    + out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0)
    + if info.address:
    + out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0)
    + self.send(out)
    + i += 1
    + nextTime += _UNREGISTER_TIME
    - def checkService(self, info):
    - """Checks the network for a unique service name, modifying the
    - ServiceInfo passed in if it is not unique."""
    - now = currentTimeMillis()
    - nextTime = now
    - i = 0
    - while i < 3:
    - for record in self.cache.entriesWithName(info.type):
    - if record.type == _TYPE_PTR and not record.isExpired(now) and record.alias == info.name:
    - if (info.name.find('.') < 0):
    - info.name = info.name + ".[" + info.address + ":" + info.port + "]." + info.type
    - self.checkService(info)
    - return
    - raise NonUniqueNameException
    - if now < nextTime:
    - self.wait(nextTime - now)
    - now = currentTimeMillis()
    - continue
    - out = DNSOutgoing(_FLAGS_QR_QUERY | _FLAGS_AA)
    - self.debug = out
    - out.addQuestion(DNSQuestion(info.type, _TYPE_PTR, _CLASS_IN))
    - out.addAuthorativeAnswer(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, info.name))
    - self.send(out)
    - i += 1
    - nextTime += _CHECK_TIME
    + def checkService(self, info):
    + """Checks the network for a unique service name, modifying the
    + ServiceInfo passed in if it is not unique."""
    + now = currentTimeMillis()
    + nextTime = now
    + i = 0
    + while i < 3:
    + for record in self.cache.entriesWithName(info.type):
    + if record.type == _TYPE_PTR and not record.isExpired(now) and record.alias == info.name:
    + if (info.name.find('.') < 0):
    + info.name = info.name + ".[" + info.address + ":" + info.port + "]." + info.type
    + self.checkService(info)
    + return
    + raise NonUniqueNameException
    + if now < nextTime:
    + self.wait(nextTime - now)
    + now = currentTimeMillis()
    + continue
    + out = DNSOutgoing(_FLAGS_QR_QUERY | _FLAGS_AA)
    + self.debug = out
    + out.addQuestion(DNSQuestion(info.type, _TYPE_PTR, _CLASS_IN))
    + out.addAuthorativeAnswer(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, info.name))
    + self.send(out)
    + i += 1
    + nextTime += _CHECK_TIME
    - def addListener(self, listener, question):
    - """Adds a listener for a given question. The listener will have
    - its updateRecord method called when information is available to
    - answer the question."""
    - now = currentTimeMillis()
    - self.listeners.append(listener)
    - if question is not None:
    - for record in self.cache.entriesWithName(question.name):
    - if question.answeredBy(record) and not record.isExpired(now):
    - listener.updateRecord(self, now, record)
    - self.notifyAll()
    + def addListener(self, listener, question):
    + """Adds a listener for a given question. The listener will have
    + its updateRecord method called when information is available to
    + answer the question."""
    + now = currentTimeMillis()
    + self.listeners.append(listener)
    + if question is not None:
    + for record in self.cache.entriesWithName(question.name):
    + if question.answeredBy(record) and not record.isExpired(now):
    + listener.updateRecord(self, now, record)
    + self.notifyAll()
    - def removeListener(self, listener):
    - """Removes a listener."""
    - try:
    - self.listeners.remove(listener)
    - self.notifyAll()
    - except:
    - pass
    + def removeListener(self, listener):
    + """Removes a listener."""
    + try:
    + self.listeners.remove(listener)
    + self.notifyAll()
    + except:
    + pass
    - def updateRecord(self, now, rec):
    - """Used to notify listeners of new information that has updated
    - a record."""
    - for listener in self.listeners:
    - listener.updateRecord(self, now, rec)
    - self.notifyAll()
    + def updateRecord(self, now, rec):
    + """Used to notify listeners of new information that has updated
    + a record."""
    + for listener in self.listeners:
    + listener.updateRecord(self, now, rec)
    + self.notifyAll()
    - def handleResponse(self, msg):
    - """Deal with incoming response packets. All answers
    - are held in the cache, and listeners are notified."""
    - now = currentTimeMillis()
    - for record in msg.answers:
    - expired = record.isExpired(now)
    - if record in self.cache.entries():
    - if expired:
    - self.cache.remove(record)
    - else:
    - entry = self.cache.get(record)
    - if entry is not None:
    - entry.resetTTL(record)
    - record = entry
    - else:
    - self.cache.add(record)
    -
    - self.updateRecord(now, record)
    + def handleResponse(self, msg):
    + """Deal with incoming response packets. All answers
    + are held in the cache, and listeners are notified."""
    + now = currentTimeMillis()
    + for record in msg.answers:
    + expired = record.isExpired(now)
    + if record in self.cache.entries():
    + if expired:
    + self.cache.remove(record)
    + else:
    + entry = self.cache.get(record)
    + if entry is not None:
    + entry.resetTTL(record)
    + record = entry
    + else:
    + self.cache.add(record)
    +
    + self.updateRecord(now, record)
    - def handleQuery(self, msg, addr, port):
    - """Deal with incoming query packets. Provides a response if
    - possible."""
    - out = None
    + def handleQuery(self, msg, addr, port):
    + """Deal with incoming query packets. Provides a response if
    + possible."""
    + out = None
    - # Support unicast client responses
    - #
    - if port != _MDNS_PORT:
    - out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA, 0)
    - for question in msg.questions:
    - out.addQuestion(question)
    -
    - for question in msg.questions:
    - if question.type == _TYPE_PTR:
    - for service in self.services.values():
    - if question.name == service.type:
    - if out is None:
    - out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
    - out.addAnswer(msg, DNSPointer(service.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, service.name))
    - else:
    - try:
    - if out is None:
    - out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
    -
    - # Answer A record queries for any service addresses we know
    - if question.type == _TYPE_A or question.type == _TYPE_ANY:
    - for service in self.services.values():
    - if service.server == question.name.lower():
    - out.addAnswer(msg, DNSAddress(question.name, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address))
    -
    - service = self.services.get(question.name.lower(), None)
    - if not service: continue
    -
    - if question.type == _TYPE_SRV or question.type == _TYPE_ANY:
    - out.addAnswer(msg, DNSService(question.name, _TYPE_SRV, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.priority, service.weight, service.port, service.server))
    - if question.type == _TYPE_TXT or question.type == _TYPE_ANY:
    - out.addAnswer(msg, DNSText(question.name, _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.text))
    - if question.type == _TYPE_SRV:
    - out.addAdditionalAnswer(DNSAddress(service.server, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address))
    - except:
    - traceback.print_exc()
    -
    - if out is not None and out.answers:
    - out.id = msg.id
    - self.send(out, addr, port)
    + # Support unicast client responses
    + #
    + if port != _MDNS_PORT:
    + out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA, 0)
    + for question in msg.questions:
    + out.addQuestion(question)
    +
    + for question in msg.questions:
    + if question.type == _TYPE_PTR:
    + for service in self.services.values():
    + if question.name == service.type:
    + if out is None:
    + out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
    + out.addAnswer(msg, DNSPointer(service.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, service.name))
    + else:
    + try:
    + if out is None:
    + out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
    +
    + # Answer A record queries for any service addresses we know
    + if question.type == _TYPE_A or question.type == _TYPE_ANY:
    + for service in self.services.values():
    + if service.server == question.name.lower():
    + out.addAnswer(msg, DNSAddress(question.name, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address))
    +
    + service = self.services.get(question.name.lower(), None)
    + if not service: continue
    +
    + if question.type == _TYPE_SRV or question.type == _TYPE_ANY:
    + out.addAnswer(msg, DNSService(question.name, _TYPE_SRV, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.priority, service.weight, service.port, service.server))
    + if question.type == _TYPE_TXT or question.type == _TYPE_ANY:
    + out.addAnswer(msg, DNSText(question.name, _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.text))
    + if question.type == _TYPE_SRV:
    + out.addAdditionalAnswer(DNSAddress(service.server, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address))
    + except:
    + traceback.print_exc()
    +
    + if out is not None and out.answers:
    + out.id = msg.id
    + self.send(out, addr, port)
    - def send(self, out, addr = _MDNS_ADDR, port = _MDNS_PORT):
    - """Sends an outgoing packet."""
    - # This is a quick test to see if we can parse the packets we generate
    - #temp = DNSIncoming(out.packet())
    - try:
    - bytes_sent = self.socket.sendto(out.packet(), 0, (addr, port))
    - except:
    - # Ignore this, it may be a temporary loss of network connection
    - pass
    + def send(self, out, addr = _MDNS_ADDR, port = _MDNS_PORT):
    + """Sends an outgoing packet."""
    + # This is a quick test to see if we can parse the packets we generate
    + #temp = DNSIncoming(out.packet())
    + try:
    + bytes_sent = self.socket.sendto(out.packet(), 0, (addr, port))
    + except:
    + # Ignore this, it may be a temporary loss of network connection
    + pass
    - def close(self):
    - """Ends the background threads, and prevent this instance from
    - servicing further queries."""
    - if globals()['_GLOBAL_DONE'] == 0:
    - globals()['_GLOBAL_DONE'] = 1
    - self.notifyAll()
    - self.engine.notify()
    - self.unregisterAllServices()
    - self.socket.setsockopt(socket.SOL_IP, socket.IP_DROP_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0'))
    - self.socket.close()
    -
    + def close(self):
    + """Ends the background threads, and prevent this instance from
    + servicing further queries."""
    + if globals()['_GLOBAL_DONE'] == 0:
    + globals()['_GLOBAL_DONE'] = 1
    + self.notifyAll()
    + self.engine.notify()
    + self.unregisterAllServices()
    + self.socket.setsockopt(socket.SOL_IP, socket.IP_DROP_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0'))
    + self.socket.close()
    +
    # Test a few module features, including service registration, service
    # query (for Zoe), and service unregistration.
    -if __name__ == '__main__':
    - print "Multicast DNS Service Discovery for Python, version", __version__
    - r = Zeroconf()
    - print "1. Testing registration of a service..."
    - desc = {'version':'0.10','a':'test value', 'b':'another value'}
    - info = ServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local.", socket.inet_aton("127.0.0.1"), 1234, 0, 0, desc)
    - print " Registering service..."
    - r.registerService(info)
    - print " Registration done."
    - print "2. Testing query of service information..."
    - print " Getting ZOE service:", str(r.getServiceInfo("_http._tcp.local.", "ZOE._http._tcp.local."))
    - print " Query done."
    - print "3. Testing query of own service..."
    - print " Getting self:", str(r.getServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local."))
    - print " Query done."
    - print "4. Testing unregister of service information..."
    - r.unregisterService(info)
    - print " Unregister done."
    - r.close()
    +if __name__ == '__main__':
    + print "Multicast DNS Service Discovery for Python, version", __version__
    + r = Zeroconf()
    + print "1. Testing registration of a service..."
    + desc = {'version':'0.10','a':'test value', 'b':'another value'}
    + info = ServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local.", socket.inet_aton("127.0.0.1"), 1234, 0, 0, desc)
    + print " Registering service..."
    + r.registerService(info)
    + print " Registration done."
    + print "2. Testing query of service information..."
    + print " Getting ZOE service:", str(r.getServiceInfo("_http._tcp.local.", "ZOE._http._tcp.local."))
    + print " Query done."
    + print "3. Testing query of own service..."
    + print " Getting self:", str(r.getServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local."))
    + print " Query done."
    + print "4. Testing unregister of service information..."
    + r.unregisterService(info)
    + print " Unregister done."
    + r.close()