@@ -27,7 +27,9 @@ class SSDPServer(DatagramProtocol):
"""A class implementing a SSDP server. The notifyReceived and
"""A class implementing a SSDP server. The notifyReceived and
searchReceived methods are called when the appropriate type of
searchReceived methods are called when the appropriate type of
datagram is received by the server."""
datagram is received by the server."""
# not used yet
stdheaders = [ ('Server', 'Twisted, UPnP/1.0, python-upnp'), ]
elements = {}
elements = {}
known = {}
known = {}
@@ -36,37 +38,40 @@ class SSDPServer(DatagramProtocol):
# Break up message in to command and headers
# Break up message in to command and headers
data = string.replace(data, '\r', '')
data = string.replace(data, ': ', ':')
lines = string.split(data, '\n')
lines = filter(lambda x: len(x) > 0, lines)
log.msg('respond to: %s:%d' % (host, port))
header, payload = data.split('\r\n\r\n')
lines = header.split('\r\n')
cmd = string.split(lines[0], ' ')
cmd = string.split(lines[0], ' ')
lines = map(lambda x: x.replace(': ', ':', 1), lines[1:])
lines = filter(lambda x: len(x) > 0, lines)
headers = dict([string.split(x, ':', 1) for x in lines[1:]])
# TODO: datagram may contain a payload, i.e Content-Length
# header is > 0. Maybe use some twisted HTTP object to do the
# parsing for us.
# SSDP discovery
headers = [string.split(x, ':', 1) for x in lines[1:]]
headers = dict(map(lambda x: (x[0].lower(), x[1]), headers))
if cmd[0] == 'M-SEARCH' and cmd[1] == '*':
if cmd[0] == 'M-SEARCH' and cmd[1] == '*':
# SSDP discovery
self.discoveryRequest(headers, (host, port))
self.discoveryRequest(headers, (host, port))
# SSDP presence
if cmd[0] == 'NOTIFY' and cmd[1] == '*':
elif cmd[0] == 'NOTIFY' and cmd[1] == '*':
# SSDP presence
self.notifyReceived(headers, (host, port))
self.notifyReceived(headers, (host, port))
else:
log.msg('Unknown SSDP command %s %s' % cmd)
def discoveryRequest(self, headers, (host, port)):
def discoveryRequest(self, headers, (host, port)):
"""Process a discovery request. The response must be sent to
"""Process a discovery request. The response must be sent to
the address specified by (host, port)."""
the address specified by (host, port)."""
log.msg('Discovery request for %s' % headers['ST '])
log.msg('Discovery request for %s' % headers['st '])
# Do we know about this service?
# Do we know about this service?
if not self.known.has_key(headers['ST']):
if headers['st'] == 'ssdp:all':
for i in self.known:
hcopy = dict(headers.iteritems())
hcopy['st'] = i
self.discoveryRequest(hcopy, (host, post))
return
if not self.known.has_key(headers['st']):
return
return
# Generate a response
# Generate a response
@@ -74,11 +79,12 @@ class SSDPServer(DatagramProtocol):
response = []
response = []
response.append('HTTP/1.1 200 OK')
response.append('HTTP/1.1 200 OK')
for k, v in self.known[headers['ST ']].items():
for k, v in self.known[headers['st ']].items():
response.append('%s: %s' % (k, v))
response.append('%s: %s' % (k, v))
log.msg('responding with: %s' % response)
log.msg('responding with: %s' % response)
# TODO: we should wait random(headers['mx'])
self.transport.write(
self.transport.write(
string.join(response, '\r\n') + '\r\n\r\n', (host, port))
string.join(response, '\r\n') + '\r\n\r\n', (host, port))
@@ -95,36 +101,54 @@ class SSDPServer(DatagramProtocol):
self.known[st]['EXT'] = ''
self.known[st]['EXT'] = ''
self.known[st]['SERVER'] = 'Twisted, UPnP/1.0, python-upnp'
self.known[st]['SERVER'] = 'Twisted, UPnP/1.0, python-upnp'
self.known[st]['CACHE-CONTROL'] = 'max-age=1800'
self.known[st]['CACHE-CONTROL'] = 'max-age=1800'
self.doNotify(st)
def doNotify(self, st):
"""Do notification"""
log.msg('Sending alive notification for %s' % st)
resp = [ 'NOTIFY * HTTP/1.1',
'Host: %s:%d' % (SSDP_ADDR, SSDP_PORT),
'NTS: ssdp:alive',
]
stcpy = dict(self.known[st].iteritems())
stcpy['NT'] = stcpy['ST']
del stcpy['ST']
resp.extend(map(lambda x: ': '.join(x), stcpy.iteritems()))
log.msg(repr(resp))
self.transport.write(
string.join(resp, '\r\n') + '\r\n\r\n', (SSDP_ADDR, SSDP_PORT))
def notifyReceived(self, headers, (host, port)):
def notifyReceived(self, headers, (host, port)):
"""Process a presence announcement. We just remember the
"""Process a presence announcement. We just remember the
details of the SSDP service announced."""
details of the SSDP service announced."""
if headers['NTS'] == 'ssdp:alive':
if headers['nts '] == 'ssdp:alive':
if not self.elements.has_key(headers['NT']):
if not self.elements.has_key(headers['nt ']):
# Register device/service
# Register device/service
self.elements[headers['NT']] = {}
self.elements[headers['NT']]['USN'] = headers['USN']
self.elements[headers['NT']]['host'] = (host, port)
self.elements[headers['nt ']] = {}
self.elements[headers['nt']]['USN'] = headers['usn ']
self.elements[headers['nt ']]['host'] = (host, port)
log.msg('Detected presence for %s' % headers['NT'])
log.msg('Detected presence for %s' % headers['nt '])
elif headers['NTS'] == 'ssdp:byebye':
elif headers['nts '] == 'ssdp:byebye':
if self.elements.has_key(headers['NT ']):
if self.elements.has_key(headers['nt ']):
# Unregister device/service
# Unregister device/service
del(self.elements[headers['NT ']])
del(self.elements[headers['nt ']])
log.msg('Detected absence for %s' % headers['NT '])
log.msg('Detected absence for %s' % headers['nt '])
else:
else:
log.msg('Unknown subtype %s for notification type %s' %
log.msg('Unknown subtype %s for notification type %s' %
(headers['NTS'], headers['NT ']))
(headers['nts'], headers['nt ']))
def findService(self, name):
def findService(self, name):
"""Return information about a service registered over SSDP."""
"""Return information about a service registered over SSDP."""