hgext/zeroconf/Zeroconf.py
changeset 43077 687b865b95ad
parent 43076 2372284d9457
child 43506 9f70512ae2cf
child 43677 0f82b29f7494
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
    74 """0.04 update - added some unit tests
    74 """0.04 update - added some unit tests
    75                  added __ne__ adjuncts where required
    75                  added __ne__ adjuncts where required
    76                  ensure names end in '.local.'
    76                  ensure names end in '.local.'
    77                  timeout on receiving socket for clean shutdown"""
    77                  timeout on receiving socket for clean shutdown"""
    78 
    78 
    79 __author__ = "Paul Scott-Murphy"
    79 __author__ = b"Paul Scott-Murphy"
    80 __email__ = "paul at scott dash murphy dot com"
    80 __email__ = b"paul at scott dash murphy dot com"
    81 __version__ = "0.12"
    81 __version__ = b"0.12"
    82 
    82 
    83 import errno
    83 import errno
    84 import itertools
    84 import itertools
    85 import select
    85 import select
    86 import socket
    86 import socket
    89 import time
    89 import time
    90 import traceback
    90 import traceback
    91 
    91 
    92 from mercurial import pycompat
    92 from mercurial import pycompat
    93 
    93 
    94 __all__ = ["Zeroconf", "ServiceInfo", "ServiceBrowser"]
    94 __all__ = [b"Zeroconf", b"ServiceInfo", b"ServiceBrowser"]
    95 
    95 
    96 # hook for threads
    96 # hook for threads
    97 
    97 
    98 globals()['_GLOBAL_DONE'] = 0
    98 globals()[b'_GLOBAL_DONE'] = 0
    99 
    99 
   100 # Some timing constants
   100 # Some timing constants
   101 
   101 
   102 _UNREGISTER_TIME = 125
   102 _UNREGISTER_TIME = 125
   103 _CHECK_TIME = 175
   103 _CHECK_TIME = 175
   158 _TYPE_ANY = 255
   158 _TYPE_ANY = 255
   159 
   159 
   160 # Mapping constants to names
   160 # Mapping constants to names
   161 
   161 
   162 _CLASSES = {
   162 _CLASSES = {
   163     _CLASS_IN: "in",
   163     _CLASS_IN: b"in",
   164     _CLASS_CS: "cs",
   164     _CLASS_CS: b"cs",
   165     _CLASS_CH: "ch",
   165     _CLASS_CH: b"ch",
   166     _CLASS_HS: "hs",
   166     _CLASS_HS: b"hs",
   167     _CLASS_NONE: "none",
   167     _CLASS_NONE: b"none",
   168     _CLASS_ANY: "any",
   168     _CLASS_ANY: b"any",
   169 }
   169 }
   170 
   170 
   171 _TYPES = {
   171 _TYPES = {
   172     _TYPE_A: "a",
   172     _TYPE_A: b"a",
   173     _TYPE_NS: "ns",
   173     _TYPE_NS: b"ns",
   174     _TYPE_MD: "md",
   174     _TYPE_MD: b"md",
   175     _TYPE_MF: "mf",
   175     _TYPE_MF: b"mf",
   176     _TYPE_CNAME: "cname",
   176     _TYPE_CNAME: b"cname",
   177     _TYPE_SOA: "soa",
   177     _TYPE_SOA: b"soa",
   178     _TYPE_MB: "mb",
   178     _TYPE_MB: b"mb",
   179     _TYPE_MG: "mg",
   179     _TYPE_MG: b"mg",
   180     _TYPE_MR: "mr",
   180     _TYPE_MR: b"mr",
   181     _TYPE_NULL: "null",
   181     _TYPE_NULL: b"null",
   182     _TYPE_WKS: "wks",
   182     _TYPE_WKS: b"wks",
   183     _TYPE_PTR: "ptr",
   183     _TYPE_PTR: b"ptr",
   184     _TYPE_HINFO: "hinfo",
   184     _TYPE_HINFO: b"hinfo",
   185     _TYPE_MINFO: "minfo",
   185     _TYPE_MINFO: b"minfo",
   186     _TYPE_MX: "mx",
   186     _TYPE_MX: b"mx",
   187     _TYPE_TXT: "txt",
   187     _TYPE_TXT: b"txt",
   188     _TYPE_AAAA: "quada",
   188     _TYPE_AAAA: b"quada",
   189     _TYPE_SRV: "srv",
   189     _TYPE_SRV: b"srv",
   190     _TYPE_ANY: "any",
   190     _TYPE_ANY: b"any",
   191 }
   191 }
   192 
   192 
   193 # utility functions
   193 # utility functions
   194 
   194 
   195 
   195 
   221     pass
   221     pass
   222 
   222 
   223 
   223 
   224 class BadDomainName(Exception):
   224 class BadDomainName(Exception):
   225     def __init__(self, pos):
   225     def __init__(self, pos):
   226         Exception.__init__(self, "at position %s" % pos)
   226         Exception.__init__(self, b"at position %s" % pos)
   227 
   227 
   228 
   228 
   229 class BadDomainNameCircular(BadDomainName):
   229 class BadDomainNameCircular(BadDomainName):
   230     pass
   230     pass
   231 
   231 
   260     def getClazz(self, clazz):
   260     def getClazz(self, clazz):
   261         """Class accessor"""
   261         """Class accessor"""
   262         try:
   262         try:
   263             return _CLASSES[clazz]
   263             return _CLASSES[clazz]
   264         except KeyError:
   264         except KeyError:
   265             return "?(%s)" % clazz
   265             return b"?(%s)" % clazz
   266 
   266 
   267     def getType(self, type):
   267     def getType(self, type):
   268         """Type accessor"""
   268         """Type accessor"""
   269         try:
   269         try:
   270             return _TYPES[type]
   270             return _TYPES[type]
   271         except KeyError:
   271         except KeyError:
   272             return "?(%s)" % type
   272             return b"?(%s)" % type
   273 
   273 
   274     def toString(self, hdr, other):
   274     def toString(self, hdr, other):
   275         """String representation with additional information"""
   275         """String representation with additional information"""
   276         result = "%s[%s,%s" % (
   276         result = b"%s[%s,%s" % (
   277             hdr,
   277             hdr,
   278             self.getType(self.type),
   278             self.getType(self.type),
   279             self.getClazz(self.clazz),
   279             self.getClazz(self.clazz),
   280         )
   280         )
   281         if self.unique:
   281         if self.unique:
   282             result += "-unique,"
   282             result += b"-unique,"
   283         else:
   283         else:
   284             result += ","
   284             result += b","
   285         result += self.name
   285         result += self.name
   286         if other is not None:
   286         if other is not None:
   287             result += ",%s]" % other
   287             result += b",%s]" % other
   288         else:
   288         else:
   289             result += "]"
   289             result += b"]"
   290         return result
   290         return result
   291 
   291 
   292 
   292 
   293 class DNSQuestion(DNSEntry):
   293 class DNSQuestion(DNSEntry):
   294     """A DNS question entry"""
   294     """A DNS question entry"""
   295 
   295 
   296     def __init__(self, name, type, clazz):
   296     def __init__(self, name, type, clazz):
   297         if pycompat.ispy3 and isinstance(name, str):
   297         if pycompat.ispy3 and isinstance(name, str):
   298             name = name.encode('ascii')
   298             name = name.encode('ascii')
   299         if not name.endswith(".local."):
   299         if not name.endswith(b".local."):
   300             raise NonLocalNameException(name)
   300             raise NonLocalNameException(name)
   301         DNSEntry.__init__(self, name, type, clazz)
   301         DNSEntry.__init__(self, name, type, clazz)
   302 
   302 
   303     def answeredBy(self, rec):
   303     def answeredBy(self, rec):
   304         """Returns true if the question is answered by the record"""
   304         """Returns true if the question is answered by the record"""
   308             and self.name == rec.name
   308             and self.name == rec.name
   309         )
   309         )
   310 
   310 
   311     def __repr__(self):
   311     def __repr__(self):
   312         """String representation"""
   312         """String representation"""
   313         return DNSEntry.toString(self, "question", None)
   313         return DNSEntry.toString(self, b"question", None)
   314 
   314 
   315 
   315 
   316 class DNSRecord(DNSEntry):
   316 class DNSRecord(DNSEntry):
   317     """A DNS record - like a DNS entry, but has a TTL"""
   317     """A DNS record - like a DNS entry, but has a TTL"""
   318 
   318 
   369         """Abstract method"""
   369         """Abstract method"""
   370         raise AbstractMethodException
   370         raise AbstractMethodException
   371 
   371 
   372     def toString(self, other):
   372     def toString(self, other):
   373         """String representation with additional information"""
   373         """String representation with additional information"""
   374         arg = "%s/%s,%s" % (
   374         arg = b"%s/%s,%s" % (
   375             self.ttl,
   375             self.ttl,
   376             self.getRemainingTTL(currentTimeMillis()),
   376             self.getRemainingTTL(currentTimeMillis()),
   377             other,
   377             other,
   378         )
   378         )
   379         return DNSEntry.toString(self, "record", arg)
   379         return DNSEntry.toString(self, b"record", arg)
   380 
   380 
   381 
   381 
   382 class DNSAddress(DNSRecord):
   382 class DNSAddress(DNSRecord):
   383     """A DNS address record"""
   383     """A DNS address record"""
   384 
   384 
   423             return self.cpu == other.cpu and self.os == other.os
   423             return self.cpu == other.cpu and self.os == other.os
   424         return 0
   424         return 0
   425 
   425 
   426     def __repr__(self):
   426     def __repr__(self):
   427         """String representation"""
   427         """String representation"""
   428         return self.cpu + " " + self.os
   428         return self.cpu + b" " + self.os
   429 
   429 
   430 
   430 
   431 class DNSPointer(DNSRecord):
   431 class DNSPointer(DNSRecord):
   432     """A DNS pointer record"""
   432     """A DNS pointer record"""
   433 
   433 
   468         return 0
   468         return 0
   469 
   469 
   470     def __repr__(self):
   470     def __repr__(self):
   471         """String representation"""
   471         """String representation"""
   472         if len(self.text) > 10:
   472         if len(self.text) > 10:
   473             return self.toString(self.text[:7] + "...")
   473             return self.toString(self.text[:7] + b"...")
   474         else:
   474         else:
   475             return self.toString(self.text)
   475             return self.toString(self.text)
   476 
   476 
   477 
   477 
   478 class DNSService(DNSRecord):
   478 class DNSService(DNSRecord):
   503             )
   503             )
   504         return 0
   504         return 0
   505 
   505 
   506     def __repr__(self):
   506     def __repr__(self):
   507         """String representation"""
   507         """String representation"""
   508         return self.toString("%s:%s" % (self.server, self.port))
   508         return self.toString(b"%s:%s" % (self.server, self.port))
   509 
   509 
   510 
   510 
   511 class DNSIncoming(object):
   511 class DNSIncoming(object):
   512     """Object representation of an incoming DNS packet"""
   512     """Object representation of an incoming DNS packet"""
   513 
   513 
   526         self.readQuestions()
   526         self.readQuestions()
   527         self.readOthers()
   527         self.readOthers()
   528 
   528 
   529     def readHeader(self):
   529     def readHeader(self):
   530         """Reads header portion of packet"""
   530         """Reads header portion of packet"""
   531         format = '!HHHHHH'
   531         format = b'!HHHHHH'
   532         length = struct.calcsize(format)
   532         length = struct.calcsize(format)
   533         info = struct.unpack(
   533         info = struct.unpack(
   534             format, self.data[self.offset : self.offset + length]
   534             format, self.data[self.offset : self.offset + length]
   535         )
   535         )
   536         self.offset += length
   536         self.offset += length
   542         self.numauthorities = info[4]
   542         self.numauthorities = info[4]
   543         self.numadditionals = info[5]
   543         self.numadditionals = info[5]
   544 
   544 
   545     def readQuestions(self):
   545     def readQuestions(self):
   546         """Reads questions section of packet"""
   546         """Reads questions section of packet"""
   547         format = '!HH'
   547         format = b'!HH'
   548         length = struct.calcsize(format)
   548         length = struct.calcsize(format)
   549         for i in range(0, self.numquestions):
   549         for i in range(0, self.numquestions):
   550             name = self.readName()
   550             name = self.readName()
   551             info = struct.unpack(
   551             info = struct.unpack(
   552                 format, self.data[self.offset : self.offset + length]
   552                 format, self.data[self.offset : self.offset + length]
   559             except NonLocalNameException:
   559             except NonLocalNameException:
   560                 pass
   560                 pass
   561 
   561 
   562     def readInt(self):
   562     def readInt(self):
   563         """Reads an integer from the packet"""
   563         """Reads an integer from the packet"""
   564         format = '!I'
   564         format = b'!I'
   565         length = struct.calcsize(format)
   565         length = struct.calcsize(format)
   566         info = struct.unpack(
   566         info = struct.unpack(
   567             format, self.data[self.offset : self.offset + length]
   567             format, self.data[self.offset : self.offset + length]
   568         )
   568         )
   569         self.offset += length
   569         self.offset += length
   575         self.offset += 1
   575         self.offset += 1
   576         return self.readString(length)
   576         return self.readString(length)
   577 
   577 
   578     def readString(self, len):
   578     def readString(self, len):
   579         """Reads a string of a given length from the packet"""
   579         """Reads a string of a given length from the packet"""
   580         format = '!%ds' % len
   580         format = b'!%ds' % len
   581         length = struct.calcsize(format)
   581         length = struct.calcsize(format)
   582         info = struct.unpack(
   582         info = struct.unpack(
   583             format, self.data[self.offset : self.offset + length]
   583             format, self.data[self.offset : self.offset + length]
   584         )
   584         )
   585         self.offset += length
   585         self.offset += length
   586         return info[0]
   586         return info[0]
   587 
   587 
   588     def readUnsignedShort(self):
   588     def readUnsignedShort(self):
   589         """Reads an unsigned short from the packet"""
   589         """Reads an unsigned short from the packet"""
   590         format = '!H'
   590         format = b'!H'
   591         length = struct.calcsize(format)
   591         length = struct.calcsize(format)
   592         info = struct.unpack(
   592         info = struct.unpack(
   593             format, self.data[self.offset : self.offset + length]
   593             format, self.data[self.offset : self.offset + length]
   594         )
   594         )
   595         self.offset += length
   595         self.offset += length
   596         return info[0]
   596         return info[0]
   597 
   597 
   598     def readOthers(self):
   598     def readOthers(self):
   599         """Reads answers, authorities and additionals section of the packet"""
   599         """Reads answers, authorities and additionals section of the packet"""
   600         format = '!HHiH'
   600         format = b'!HHiH'
   601         length = struct.calcsize(format)
   601         length = struct.calcsize(format)
   602         n = self.numanswers + self.numauthorities + self.numadditionals
   602         n = self.numanswers + self.numauthorities + self.numadditionals
   603         for i in range(0, n):
   603         for i in range(0, n):
   604             domain = self.readName()
   604             domain = self.readName()
   605             info = struct.unpack(
   605             info = struct.unpack(
   744         """Adds an additional answer"""
   744         """Adds an additional answer"""
   745         self.additionals.append(record)
   745         self.additionals.append(record)
   746 
   746 
   747     def writeByte(self, value):
   747     def writeByte(self, value):
   748         """Writes a single byte to the packet"""
   748         """Writes a single byte to the packet"""
   749         format = '!c'
   749         format = b'!c'
   750         self.data.append(struct.pack(format, chr(value)))
   750         self.data.append(struct.pack(format, chr(value)))
   751         self.size += 1
   751         self.size += 1
   752 
   752 
   753     def insertShort(self, index, value):
   753     def insertShort(self, index, value):
   754         """Inserts an unsigned short in a certain position in the packet"""
   754         """Inserts an unsigned short in a certain position in the packet"""
   755         format = '!H'
   755         format = b'!H'
   756         self.data.insert(index, struct.pack(format, value))
   756         self.data.insert(index, struct.pack(format, value))
   757         self.size += 2
   757         self.size += 2
   758 
   758 
   759     def writeShort(self, value):
   759     def writeShort(self, value):
   760         """Writes an unsigned short to the packet"""
   760         """Writes an unsigned short to the packet"""
   761         format = '!H'
   761         format = b'!H'
   762         self.data.append(struct.pack(format, value))
   762         self.data.append(struct.pack(format, value))
   763         self.size += 2
   763         self.size += 2
   764 
   764 
   765     def writeInt(self, value):
   765     def writeInt(self, value):
   766         """Writes an unsigned integer to the packet"""
   766         """Writes an unsigned integer to the packet"""
   767         format = '!I'
   767         format = b'!I'
   768         self.data.append(struct.pack(format, int(value)))
   768         self.data.append(struct.pack(format, int(value)))
   769         self.size += 4
   769         self.size += 4
   770 
   770 
   771     def writeString(self, value, length):
   771     def writeString(self, value, length):
   772         """Writes a string to the packet"""
   772         """Writes a string to the packet"""
   773         format = '!' + str(length) + 's'
   773         format = b'!' + str(length) + b's'
   774         self.data.append(struct.pack(format, value))
   774         self.data.append(struct.pack(format, value))
   775         self.size += length
   775         self.size += length
   776 
   776 
   777     def writeUTF(self, s):
   777     def writeUTF(self, s):
   778         """Writes a UTF-8 string of a given length to the packet"""
   778         """Writes a UTF-8 string of a given length to the packet"""
   794             # No record of this name already, so write it
   794             # No record of this name already, so write it
   795             # out as normal, recording the location of the name
   795             # out as normal, recording the location of the name
   796             # for future pointers to it.
   796             # for future pointers to it.
   797             #
   797             #
   798             self.names[name] = self.size
   798             self.names[name] = self.size
   799             parts = name.split('.')
   799             parts = name.split(b'.')
   800             if parts[-1] == '':
   800             if parts[-1] == b'':
   801                 parts = parts[:-1]
   801                 parts = parts[:-1]
   802             for part in parts:
   802             for part in parts:
   803                 self.writeUTF(part)
   803                 self.writeUTF(part)
   804             self.writeByte(0)
   804             self.writeByte(0)
   805             return
   805             return
   833         #
   833         #
   834         self.size += 2
   834         self.size += 2
   835         record.write(self)
   835         record.write(self)
   836         self.size -= 2
   836         self.size -= 2
   837 
   837 
   838         length = len(''.join(self.data[index:]))
   838         length = len(b''.join(self.data[index:]))
   839         self.insertShort(index, length)  # Here is the short we adjusted for
   839         self.insertShort(index, length)  # Here is the short we adjusted for
   840 
   840 
   841     def packet(self):
   841     def packet(self):
   842         """Returns a string containing the packet's bytes
   842         """Returns a string containing the packet's bytes
   843 
   843 
   861             self.insertShort(0, self.flags)
   861             self.insertShort(0, self.flags)
   862             if self.multicast:
   862             if self.multicast:
   863                 self.insertShort(0, 0)
   863                 self.insertShort(0, 0)
   864             else:
   864             else:
   865                 self.insertShort(0, self.id)
   865                 self.insertShort(0, self.id)
   866         return ''.join(self.data)
   866         return b''.join(self.data)
   867 
   867 
   868 
   868 
   869 class DNSCache(object):
   869 class DNSCache(object):
   870     """A cache of DNS entries"""
   870     """A cache of DNS entries"""
   871 
   871 
   937         self.timeout = 5
   937         self.timeout = 5
   938         self.condition = threading.Condition()
   938         self.condition = threading.Condition()
   939         self.start()
   939         self.start()
   940 
   940 
   941     def run(self):
   941     def run(self):
   942         while not globals()['_GLOBAL_DONE']:
   942         while not globals()[b'_GLOBAL_DONE']:
   943             rs = self.getReaders()
   943             rs = self.getReaders()
   944             if len(rs) == 0:
   944             if len(rs) == 0:
   945                 # No sockets to manage, but we wait for the timeout
   945                 # No sockets to manage, but we wait for the timeout
   946                 # or addition of a socket
   946                 # or addition of a socket
   947                 #
   947                 #
   953                     rr, wr, er = select.select(rs, [], [], self.timeout)
   953                     rr, wr, er = select.select(rs, [], [], self.timeout)
   954                     for sock in rr:
   954                     for sock in rr:
   955                         try:
   955                         try:
   956                             self.readers[sock].handle_read()
   956                             self.readers[sock].handle_read()
   957                         except Exception:
   957                         except Exception:
   958                             if not globals()['_GLOBAL_DONE']:
   958                             if not globals()[b'_GLOBAL_DONE']:
   959                                 traceback.print_exc()
   959                                 traceback.print_exc()
   960                 except Exception:
   960                 except Exception:
   961                     pass
   961                     pass
   962 
   962 
   963     def getReaders(self):
   963     def getReaders(self):
  1033         self.start()
  1033         self.start()
  1034 
  1034 
  1035     def run(self):
  1035     def run(self):
  1036         while True:
  1036         while True:
  1037             self.zeroconf.wait(10 * 1000)
  1037             self.zeroconf.wait(10 * 1000)
  1038             if globals()['_GLOBAL_DONE']:
  1038             if globals()[b'_GLOBAL_DONE']:
  1039                 return
  1039                 return
  1040             now = currentTimeMillis()
  1040             now = currentTimeMillis()
  1041             for record in self.zeroconf.cache.entries():
  1041             for record in self.zeroconf.cache.entries():
  1042                 if record.isExpired(now):
  1042                 if record.isExpired(now):
  1043                     self.zeroconf.updateRecord(now, record)
  1043                     self.zeroconf.updateRecord(now, record)
  1106         while True:
  1106         while True:
  1107             event = None
  1107             event = None
  1108             now = currentTimeMillis()
  1108             now = currentTimeMillis()
  1109             if len(self.list) == 0 and self.nexttime > now:
  1109             if len(self.list) == 0 and self.nexttime > now:
  1110                 self.zeroconf.wait(self.nexttime - now)
  1110                 self.zeroconf.wait(self.nexttime - now)
  1111             if globals()['_GLOBAL_DONE'] or self.done:
  1111             if globals()[b'_GLOBAL_DONE'] or self.done:
  1112                 return
  1112                 return
  1113             now = currentTimeMillis()
  1113             now = currentTimeMillis()
  1114 
  1114 
  1115             if self.nexttime <= now:
  1115             if self.nexttime <= now:
  1116                 out = DNSOutgoing(_FLAGS_QR_QUERY)
  1116                 out = DNSOutgoing(_FLAGS_QR_QUERY)
  1172     def setProperties(self, properties):
  1172     def setProperties(self, properties):
  1173         """Sets properties and text of this info from a dictionary"""
  1173         """Sets properties and text of this info from a dictionary"""
  1174         if isinstance(properties, dict):
  1174         if isinstance(properties, dict):
  1175             self.properties = properties
  1175             self.properties = properties
  1176             list = []
  1176             list = []
  1177             result = ''
  1177             result = b''
  1178             for key in properties:
  1178             for key in properties:
  1179                 value = properties[key]
  1179                 value = properties[key]
  1180                 if value is None:
  1180                 if value is None:
  1181                     suffix = ''
  1181                     suffix = b''
  1182                 elif isinstance(value, str):
  1182                 elif isinstance(value, str):
  1183                     suffix = value
  1183                     suffix = value
  1184                 elif isinstance(value, int):
  1184                 elif isinstance(value, int):
  1185                     if value:
  1185                     if value:
  1186                         suffix = 'true'
  1186                         suffix = b'true'
  1187                     else:
  1187                     else:
  1188                         suffix = 'false'
  1188                         suffix = b'false'
  1189                 else:
  1189                 else:
  1190                     suffix = ''
  1190                     suffix = b''
  1191                 list.append('='.join((key, suffix)))
  1191                 list.append(b'='.join((key, suffix)))
  1192             for item in list:
  1192             for item in list:
  1193                 result = ''.join(
  1193                 result = b''.join(
  1194                     (result, struct.pack('!c', chr(len(item))), item)
  1194                     (result, struct.pack(b'!c', chr(len(item))), item)
  1195                 )
  1195                 )
  1196             self.text = result
  1196             self.text = result
  1197         else:
  1197         else:
  1198             self.text = properties
  1198             self.text = properties
  1199 
  1199 
  1210                 index += 1
  1210                 index += 1
  1211                 strs.append(text[index : index + length])
  1211                 strs.append(text[index : index + length])
  1212                 index += length
  1212                 index += length
  1213 
  1213 
  1214             for s in strs:
  1214             for s in strs:
  1215                 eindex = s.find('=')
  1215                 eindex = s.find(b'=')
  1216                 if eindex == -1:
  1216                 if eindex == -1:
  1217                     # No equals sign at all
  1217                     # No equals sign at all
  1218                     key = s
  1218                     key = s
  1219                     value = 0
  1219                     value = 0
  1220                 else:
  1220                 else:
  1221                     key = s[:eindex]
  1221                     key = s[:eindex]
  1222                     value = s[eindex + 1 :]
  1222                     value = s[eindex + 1 :]
  1223                     if value == 'true':
  1223                     if value == b'true':
  1224                         value = 1
  1224                         value = 1
  1225                     elif value == 'false' or not value:
  1225                     elif value == b'false' or not value:
  1226                         value = 0
  1226                         value = 0
  1227 
  1227 
  1228                 # Only update non-existent properties
  1228                 # Only update non-existent properties
  1229                 if key and result.get(key) is None:
  1229                 if key and result.get(key) is None:
  1230                     result[key] = value
  1230                     result[key] = value
  1238         """Type accessor"""
  1238         """Type accessor"""
  1239         return self.type
  1239         return self.type
  1240 
  1240 
  1241     def getName(self):
  1241     def getName(self):
  1242         """Name accessor"""
  1242         """Name accessor"""
  1243         if self.type is not None and self.name.endswith("." + self.type):
  1243         if self.type is not None and self.name.endswith(b"." + self.type):
  1244             return self.name[: len(self.name) - len(self.type) - 1]
  1244             return self.name[: len(self.name) - len(self.type) - 1]
  1245         return self.name
  1245         return self.name
  1246 
  1246 
  1247     def getAddress(self):
  1247     def getAddress(self):
  1248         """Address accessor"""
  1248         """Address accessor"""
  1366         """Non-equality test"""
  1366         """Non-equality test"""
  1367         return not self.__eq__(other)
  1367         return not self.__eq__(other)
  1368 
  1368 
  1369     def __repr__(self):
  1369     def __repr__(self):
  1370         """String representation"""
  1370         """String representation"""
  1371         result = "service[%s,%s:%s," % (
  1371         result = b"service[%s,%s:%s," % (
  1372             self.name,
  1372             self.name,
  1373             socket.inet_ntoa(self.getAddress()),
  1373             socket.inet_ntoa(self.getAddress()),
  1374             self.port,
  1374             self.port,
  1375         )
  1375         )
  1376         if self.text is None:
  1376         if self.text is None:
  1377             result += "None"
  1377             result += b"None"
  1378         else:
  1378         else:
  1379             if len(self.text) < 20:
  1379             if len(self.text) < 20:
  1380                 result += self.text
  1380                 result += self.text
  1381             else:
  1381             else:
  1382                 result += self.text[:17] + "..."
  1382                 result += self.text[:17] + b"..."
  1383         result += "]"
  1383         result += b"]"
  1384         return result
  1384         return result
  1385 
  1385 
  1386 
  1386 
  1387 class Zeroconf(object):
  1387 class Zeroconf(object):
  1388     """Implementation of Zeroconf Multicast DNS Service Discovery
  1388     """Implementation of Zeroconf Multicast DNS Service Discovery
  1391     """
  1391     """
  1392 
  1392 
  1393     def __init__(self, bindaddress=None):
  1393     def __init__(self, bindaddress=None):
  1394         """Creates an instance of the Zeroconf class, establishing
  1394         """Creates an instance of the Zeroconf class, establishing
  1395         multicast communications, listening and reaping threads."""
  1395         multicast communications, listening and reaping threads."""
  1396         globals()['_GLOBAL_DONE'] = 0
  1396         globals()[b'_GLOBAL_DONE'] = 0
  1397         if bindaddress is None:
  1397         if bindaddress is None:
  1398             self.intf = socket.gethostbyname(socket.gethostname())
  1398             self.intf = socket.gethostbyname(socket.gethostname())
  1399         else:
  1399         else:
  1400             self.intf = bindaddress
  1400             self.intf = bindaddress
  1401         self.group = ('', _MDNS_PORT)
  1401         self.group = (b'', _MDNS_PORT)
  1402         self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  1402         self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  1403         try:
  1403         try:
  1404             self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  1404             self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  1405             self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
  1405             self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
  1406         except Exception:
  1406         except Exception:
  1412             # if you're on a BSD-based system, and haven't upgraded
  1412             # if you're on a BSD-based system, and haven't upgraded
  1413             # to Python 2.3 yet, you may find this library doesn't
  1413             # to Python 2.3 yet, you may find this library doesn't
  1414             # work as expected.
  1414             # work as expected.
  1415             #
  1415             #
  1416             pass
  1416             pass
  1417         self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, "\xff")
  1417         self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, b"\xff")
  1418         self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, "\x01")
  1418         self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, b"\x01")
  1419         try:
  1419         try:
  1420             self.socket.bind(self.group)
  1420             self.socket.bind(self.group)
  1421         except Exception:
  1421         except Exception:
  1422             # Some versions of linux raise an exception even though
  1422             # Some versions of linux raise an exception even though
  1423             # SO_REUSEADDR and SO_REUSEPORT have been set, so ignore it
  1423             # SO_REUSEADDR and SO_REUSEPORT have been set, so ignore it
  1440         self.engine = Engine(self)
  1440         self.engine = Engine(self)
  1441         self.listener = Listener(self)
  1441         self.listener = Listener(self)
  1442         self.reaper = Reaper(self)
  1442         self.reaper = Reaper(self)
  1443 
  1443 
  1444     def isLoopback(self):
  1444     def isLoopback(self):
  1445         return self.intf.startswith("127.0.0.1")
  1445         return self.intf.startswith(b"127.0.0.1")
  1446 
  1446 
  1447     def isLinklocal(self):
  1447     def isLinklocal(self):
  1448         return self.intf.startswith("169.254.")
  1448         return self.intf.startswith(b"169.254.")
  1449 
  1449 
  1450     def wait(self, timeout):
  1450     def wait(self, timeout):
  1451         """Calling thread waits for a given number of milliseconds or
  1451         """Calling thread waits for a given number of milliseconds or
  1452         until notified."""
  1452         until notified."""
  1453         self.condition.acquire()
  1453         self.condition.acquire()
  1640                 if (
  1640                 if (
  1641                     record.type == _TYPE_PTR
  1641                     record.type == _TYPE_PTR
  1642                     and not record.isExpired(now)
  1642                     and not record.isExpired(now)
  1643                     and record.alias == info.name
  1643                     and record.alias == info.name
  1644                 ):
  1644                 ):
  1645                     if info.name.find('.') < 0:
  1645                     if info.name.find(b'.') < 0:
  1646                         info.name = "%w.[%s:%d].%s" % (
  1646                         info.name = b"%w.[%s:%d].%s" % (
  1647                             info.name,
  1647                             info.name,
  1648                             info.address,
  1648                             info.address,
  1649                             info.port,
  1649                             info.port,
  1650                             info.type,
  1650                             info.type,
  1651                         )
  1651                         )
  1724             for question in msg.questions:
  1724             for question in msg.questions:
  1725                 out.addQuestion(question)
  1725                 out.addQuestion(question)
  1726 
  1726 
  1727         for question in msg.questions:
  1727         for question in msg.questions:
  1728             if question.type == _TYPE_PTR:
  1728             if question.type == _TYPE_PTR:
  1729                 if question.name == "_services._dns-sd._udp.local.":
  1729                 if question.name == b"_services._dns-sd._udp.local.":
  1730                     for stype in self.servicetypes.keys():
  1730                     for stype in self.servicetypes.keys():
  1731                         if out is None:
  1731                         if out is None:
  1732                             out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
  1732                             out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
  1733                         out.addAnswer(
  1733                         out.addAnswer(
  1734                             msg,
  1734                             msg,
  1735                             DNSPointer(
  1735                             DNSPointer(
  1736                                 "_services._dns-sd._udp.local.",
  1736                                 b"_services._dns-sd._udp.local.",
  1737                                 _TYPE_PTR,
  1737                                 _TYPE_PTR,
  1738                                 _CLASS_IN,
  1738                                 _CLASS_IN,
  1739                                 _DNS_TTL,
  1739                                 _DNS_TTL,
  1740                                 stype,
  1740                                 stype,
  1741                             ),
  1741                             ),
  1831             pass
  1831             pass
  1832 
  1832 
  1833     def close(self):
  1833     def close(self):
  1834         """Ends the background threads, and prevent this instance from
  1834         """Ends the background threads, and prevent this instance from
  1835         servicing further queries."""
  1835         servicing further queries."""
  1836         if globals()['_GLOBAL_DONE'] == 0:
  1836         if globals()[b'_GLOBAL_DONE'] == 0:
  1837             globals()['_GLOBAL_DONE'] = 1
  1837             globals()[b'_GLOBAL_DONE'] = 1
  1838             self.notifyAll()
  1838             self.notifyAll()
  1839             self.engine.notify()
  1839             self.engine.notify()
  1840             self.unregisterAllServices()
  1840             self.unregisterAllServices()
  1841             self.socket.setsockopt(
  1841             self.socket.setsockopt(
  1842                 socket.SOL_IP,
  1842                 socket.SOL_IP,
  1848 
  1848 
  1849 # Test a few module features, including service registration, service
  1849 # Test a few module features, including service registration, service
  1850 # query (for Zoe), and service unregistration.
  1850 # query (for Zoe), and service unregistration.
  1851 
  1851 
  1852 if __name__ == '__main__':
  1852 if __name__ == '__main__':
  1853     print("Multicast DNS Service Discovery for Python, version", __version__)
  1853     print(b"Multicast DNS Service Discovery for Python, version", __version__)
  1854     r = Zeroconf()
  1854     r = Zeroconf()
  1855     print("1. Testing registration of a service...")
  1855     print(b"1. Testing registration of a service...")
  1856     desc = {'version': '0.10', 'a': 'test value', 'b': 'another value'}
  1856     desc = {b'version': b'0.10', b'a': b'test value', b'b': b'another value'}
  1857     info = ServiceInfo(
  1857     info = ServiceInfo(
  1858         "_http._tcp.local.",
  1858         b"_http._tcp.local.",
  1859         "My Service Name._http._tcp.local.",
  1859         b"My Service Name._http._tcp.local.",
  1860         socket.inet_aton("127.0.0.1"),
  1860         socket.inet_aton(b"127.0.0.1"),
  1861         1234,
  1861         1234,
  1862         0,
  1862         0,
  1863         0,
  1863         0,
  1864         desc,
  1864         desc,
  1865     )
  1865     )
  1866     print("   Registering service...")
  1866     print(b"   Registering service...")
  1867     r.registerService(info)
  1867     r.registerService(info)
  1868     print("   Registration done.")
  1868     print(b"   Registration done.")
  1869     print("2. Testing query of service information...")
  1869     print(b"2. Testing query of service information...")
  1870     print(
  1870     print(
  1871         "   Getting ZOE service:",
  1871         b"   Getting ZOE service:",
  1872         str(r.getServiceInfo("_http._tcp.local.", "ZOE._http._tcp.local.")),
  1872         str(r.getServiceInfo(b"_http._tcp.local.", b"ZOE._http._tcp.local.")),
  1873     )
  1873     )
  1874     print("   Query done.")
  1874     print(b"   Query done.")
  1875     print("3. Testing query of own service...")
  1875     print(b"3. Testing query of own service...")
  1876     print(
  1876     print(
  1877         "   Getting self:",
  1877         b"   Getting self:",
  1878         str(
  1878         str(
  1879             r.getServiceInfo(
  1879             r.getServiceInfo(
  1880                 "_http._tcp.local.", "My Service Name._http._tcp.local."
  1880                 b"_http._tcp.local.", b"My Service Name._http._tcp.local."
  1881             )
  1881             )
  1882         ),
  1882         ),
  1883     )
  1883     )
  1884     print("   Query done.")
  1884     print(b"   Query done.")
  1885     print("4. Testing unregister of service information...")
  1885     print(b"4. Testing unregister of service information...")
  1886     r.unregisterService(info)
  1886     r.unregisterService(info)
  1887     print("   Unregister done.")
  1887     print(b"   Unregister done.")
  1888     r.close()
  1888     r.close()