A Python UPnP Media Server
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

144 lines
4.4 KiB

  1. # Licensed under the MIT license
  2. # http://opensource.org/licenses/mit-license.php
  3. # (c) 2005, Tim Potter <tpot@samba.org>
  4. #
  5. # Implementation of SSDP server under Twisted Python.
  6. #
  7. import string
  8. from twisted.python import log
  9. from twisted.internet.protocol import DatagramProtocol
  10. # TODO: Is there a better way of hooking the SSDPServer into a reactor
  11. # without having to know the default SSDP port and multicast address?
  12. # There must be a twisted idiom for doing this.
  13. SSDP_PORT = 1900
  14. SSDP_ADDR = '239.255.255.250'
  15. # TODO: Break out into a HTTPOverUDP class and implement
  16. # process_SEARCH(), process_NOTIFY() methods. Create UPNP specific
  17. # class to handle services etc.
  18. class SSDPServer(DatagramProtocol):
  19. """A class implementing a SSDP server. The notifyReceived and
  20. searchReceived methods are called when the appropriate type of
  21. datagram is received by the server."""
  22. elements = {}
  23. known = {}
  24. def datagramReceived(self, data, (host, port)):
  25. """Handle a received multicast datagram."""
  26. # Break up message in to command and headers
  27. data = string.replace(data, '\r', '')
  28. data = string.replace(data, ': ', ':')
  29. lines = string.split(data, '\n')
  30. lines = filter(lambda x: len(x) > 0, lines)
  31. cmd = string.split(lines[0], ' ')
  32. headers = dict([string.split(x, ':', 1) for x in lines[1:]])
  33. # TODO: datagram may contain a payload, i.e Content-Length
  34. # header is > 0. Maybe use some twisted HTTP object to do the
  35. # parsing for us.
  36. # SSDP discovery
  37. if cmd[0] == 'M-SEARCH' and cmd[1] == '*':
  38. self.discoveryRequest(headers, (host, port))
  39. # SSDP presence
  40. if cmd[0] == 'NOTIFY' and cmd[1] == '*':
  41. self.notifyReceived(headers, (host, port))
  42. def discoveryRequest(self, headers, (host, port)):
  43. """Process a discovery request. The response must be sent to
  44. the address specified by (host, port)."""
  45. log.msg('Discovery request for %s' % headers['ST'])
  46. # Do we know about this service?
  47. if not self.known.has_key(headers['ST']):
  48. return
  49. # Generate a response
  50. response = []
  51. response.append('HTTP/1.1 200 OK')
  52. for k, v in self.known[headers['ST']].items():
  53. response.append('%s: %s' % (k, v))
  54. log.msg('responding with: %s' % response)
  55. self.transport.write(
  56. string.join(response, '\r\n') + '\r\n\r\n', (host, port))
  57. def register(self, usn, st, location):
  58. """Register a service or device that this SSDP server will
  59. respond to."""
  60. log.msg('Registering %s' % st)
  61. self.known[st] = {}
  62. self.known[st]['USN'] = usn
  63. self.known[st]['LOCATION'] = location
  64. self.known[st]['ST'] = st
  65. self.known[st]['EXT'] = ''
  66. self.known[st]['SERVER'] = 'Twisted, UPnP/1.0, python-upnp'
  67. self.known[st]['CACHE-CONTROL'] = 'max-age=1800'
  68. def notifyReceived(self, headers, (host, port)):
  69. """Process a presence announcement. We just remember the
  70. details of the SSDP service announced."""
  71. if headers['NTS'] == 'ssdp:alive':
  72. if not self.elements.has_key(headers['NT']):
  73. # Register device/service
  74. self.elements[headers['NT']] = {}
  75. self.elements[headers['NT']]['USN'] = headers['USN']
  76. self.elements[headers['NT']]['host'] = (host, port)
  77. log.msg('Detected presence for %s' % headers['NT'])
  78. elif headers['NTS'] == 'ssdp:byebye':
  79. if self.elements.has_key(headers['NT']):
  80. # Unregister device/service
  81. del(self.elements[headers['NT']])
  82. log.msg('Detected absence for %s' % headers['NT'])
  83. else:
  84. log.msg('Unknown subtype %s for notification type %s' %
  85. (headers['NTS'], headers['NT']))
  86. def findService(self, name):
  87. """Return information about a service registered over SSDP."""
  88. # TODO: Implement me.
  89. # TODO: Send out a discovery request if we haven't registered
  90. # a presence announcement.
  91. def findDevice(self, name):
  92. """Return information about a device registered over SSDP."""
  93. # TODO: Implement me.
  94. # TODO: Send out a discovery request if we haven't registered
  95. # a presence announcement.