Commit 9d298418 authored by Michael Weinrich's avatar Michael Weinrich
Browse files

Event notify messages are checked

parent 4f9c9341
# A simulation of Subversion default ignores, generated by reposurgeon.
*.o
*.lo
*.la
*.al
.libs
*.so
*.so.[0-9]*
*.a
build
temp
dist
*.egg-info
*.pyc
*.pyo
*.rej
*~
.#*
.*.swp
.DS_store
test.py
*log*
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns="urn:schemas-upnp-org:device-1-0"
<xs:schema
targetNamespace="urn:schemas-upnp-org:device-1-0"
elementFormDefault="qualified">
xmlns:tns="urn:schemas-upnp-org:device-1-0"
xmlns="urn:schemas-upnp-org:device-1-0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:dlna="urn:schemas-dlna-org:device-1-0"
attributeFormDefault="qualified" elementFormDefault="qualified">
<xs:annotation>
<xs:documentation>
XML Schema for UPnP device descriptions in real XSD format
......@@ -49,7 +52,7 @@
<xs:element name="presentationURL" type="xs:string" minOccurs="0" maxOccurs="1" />
</xs:all>
</xs:complexType>
<xs:complexType name="IconListType">
<xs:sequence>
<xs:element name="icon" minOccurs="1" maxOccurs="unbounded">
......
<?xml version="1.0" ?>
<Schema name="urn:schemas-upnp-org:event-1-0"
xmlns="urn:schemas-microsoft-com:xml-data"
xmlns:dt="urn:schemas-microsoft-com:datatypes">
<ElementType name="propertyset" content="eltOnly">
<element type="property" minOccurs="1" maxOccurs="*" />
</ElementType>
<ElementType name="property" content="eltOnly" />
</Schema>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<xs:schema id="propertyset"
targetNamespace="urn:schemas-upnp-org:event-1-0"
xmlns:tns="urn:schemas-upnp-org:event-1-0"
xmlns="urn:schemas-upnp-org:event-1-0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
attributeFormDefault="qualified" elementFormDefault="qualified">
<xs:element name="propertyset">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="property">
<xs:complexType>
<xs:sequence>
<xs:any namespace="##local" minOccurs="0" maxOccurs="unbounded" processContents="lax"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<root xmlns="urn:schemas-upnp-org:device-1-0">
<specVersion>
<major>1</major>
<minor>0</minor>
</specVersion>
<device>
<deviceType>
urn:schemas-upnp-org:device:MediaServer:1
</deviceType>
<friendlyName>uShare</friendlyName>
<manufacturer>GeeXboX Team</manufacturer>
<manufacturerURL>http://ushare.geexbox.org/</manufacturerURL>
<modelDescription>
GeeXboX uShare : UPnP Media Server
</modelDescription>
<modelName>uShare</modelName>
<modelNumber>001</modelNumber>
<modelURL>http://ushare.geexbox.org/</modelURL>
<serialNumber>GEEXBOX-USHARE-01</serialNumber>
<laber>buh</laber>
<UDN>uuid:0006290F94F2</UDN>
<serviceList>
<service>
<serviceType>
urn:schemas-upnp-org:service:ConnectionManager:1
</serviceType>
<serviceId>
urn:upnp-org:serviceId:ConnectionManager
</serviceId>
<SCPDURL>/web/cms.xml</SCPDURL>
<controlURL>/web/cms_control</controlURL>
<eventSubURL>/web/cms_event</eventSubURL>
</service>
<service>
<serviceType>
urn:schemas-upnp-org:service:ContentDirectory:1
</serviceType>
<serviceId>
urn:upnp-org:serviceId:ContentDirectory
</serviceId>
<SCPDURL>/web/cds.xml</SCPDURL>
<controlURL>/web/cds_control</controlURL>
<eventSubURL>/web/cds_event</eventSubURL>
</service>
<service>
<serviceType>
urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1
</serviceType>
<serviceId>
urn:microsoft.com:serviceId:X_MS_MediaReceiverRegistrar
</serviceId>
<SCPDURL>/web/msr.xml</SCPDURL>
<controlURL>/web/msr_control</controlURL>
<eventSubURL>/web/msr_event</eventSubURL>
</service>
</serviceList>
<presentationURL>/web/ushare.html</presentationURL>
</device>
<URLBase>http://192.168.82.7:49152/</URLBase>
<specVersion>
<major>1</major>
<minor>0</minor>
</specVersion>
<device>
<dlna:X_DLNACAP xmlns:dlna="urn:schemas-dlna-org:device-1-0"></dlna:X_DLNACAP>
<dlna:X_DLNADOC xmlns:dlna="urn:schemas-dlna-org:device-1-0">
DMS-1.50
</dlna:X_DLNADOC>
<deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType>
<UDN>uuid:aa5674a6-0d98-48cd-9c9b-a8aa193cdba8</UDN>
<presentationURL>/web/</presentationURL>
<friendlyName>Nero MediaHome (vulcan)</friendlyName>
<manufacturer>Nero AG</manufacturer>
<manufacturerURL>http://www.nero.com/</manufacturerURL>
<modelDescription>Nero MediaHome</modelDescription>
<modelName>Nero MediaHome</modelName>
<modelURL>http://www.nero.com/</modelURL>
<modelNumber>1.0</modelNumber>
<UPC>urn:schemas-nero-com:upc:nms-1-0</UPC>
<iconList>
<icon>
<mimetype>image/png</mimetype>
<width>120</width>
<height>120</height>
<depth>24</depth>
<url>/deviceicon-PNG_LRG_ICO.png</url>
</icon>
<icon>
<mimetype>image/png</mimetype>
<width>48</width>
<height>48</height>
<depth>24</depth>
<url>/deviceicon-PNG_SM_ICO.png</url>
</icon>
<icon>
<mimetype>image/jpeg</mimetype>
<width>120</width>
<height>120</height>
<depth>24</depth>
<url>/deviceicon-JPEG_LRG_ICO.jpg</url>
</icon>
<icon>
<mimetype>image/jpeg</mimetype>
<width>48</width>
<height>48</height>
<depth>24</depth>
<url>/deviceicon-JPEG_SM_ICO.jpg</url>
</icon>
</iconList>
<serviceList>
<service>
<serviceType>
urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1
</serviceType>
<serviceId>
urn:microsoft.com:serviceId:X_MS_MediaReceiverRegistrar
</serviceId>
<SCPDURL>
/upnp/services/X_MS_MediaReceiverRegistrar/scpd.xml
</SCPDURL>
<controlURL>
/upnp/services/X_MS_MediaReceiverRegistrar/control
</controlURL>
<eventSubURL>
/upnp/services/X_MS_MediaReceiverRegistrar/event
</eventSubURL>
</service>
<service>
<serviceType>
urn:schemas-upnp-org:service:ConnectionManager:1
</serviceType>
<serviceId>urn:upnp-org:serviceId:ConnectionManager</serviceId>
<SCPDURL>/upnp/services/ConnectionManager/scpd.xml</SCPDURL>
<controlURL>
/upnp/services/ConnectionManager/control
</controlURL>
<eventSubURL>
/upnp/services/ConnectionManager/event
</eventSubURL>
</service>
<service>
<serviceType>
urn:schemas-upnp-org:service:ContentDirectory:1
</serviceType>
<serviceId>urn:upnp-org:serviceId:ContentDirectory</serviceId>
<SCPDURL>/upnp/services/ContentDirectory/scpd.xml</SCPDURL>
<controlURL>/upnp/services/ContentDirectory/control</controlURL>
<eventSubURL>/upnp/services/ContentDirectory/event</eventSubURL>
</service>
</serviceList>
<deviceList />
</device>
</root>
<e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0">
<e:property>
<PossibleConnectionTypes>
Unconfigured,IP_Routed,DHCP_Spoofed,PPPOE_Bridged,PPTP_Relay,L2TP_Relay,PPOE_Relay
</PossibleConnectionTypes>
</e:property>
<e:property>
<ConnectionStatus>Unconfigured</ConnectionStatus>
</e:property>
<e:property>
<ExternalIPAddress>88.74.134.46</ExternalIPAddress>
</e:property>
<e:property>
<PortMappingNumberOfEntries></PortMappingNumberOfEntries>
</e:property>
</e:propertyset>
\ No newline at end of file
......@@ -53,7 +53,7 @@ class UPnT(log.Loggable):
"""
if not self.known_hosts.has_key(ip):
self.known_hosts[ip] = Host(ip, self._config)
self.known_hosts[ip] = Host(ip, self._config, self._coherence)
self.info('Host added', ip)
self.debug('Calling dispatchDiscoveryPacketData')
#try:
......
# Licensed under the MIT license
# http://opensource.org/licenses/mit-license.php
# Copyright 2007, Michael Weinrich <testsuite@michael-weinrich.de>
from coherence import log
import louie
from twisted.internet import reactor
class ServerControl(log.Loggable):
logCategory = 'UPnT_ServerControl'
class ClientControl(log.Loggable):
logCategory = 'UPnT_ServerControl'
......@@ -4,10 +4,14 @@
# Copyright 2007, Michael Weinrich <testsuite@michael-weinrich.de>
from coherence import log
from coherence.upnp.core import event
from coherence.upnp.devices.control_point import ControlPoint
import urllib2
import louie
from lxml import etree
from twisted.internet import reactor
from StringIO import StringIO
class ServerEventMessageChecks(log.Loggable):
"""
......@@ -18,10 +22,17 @@ class ServerEventMessageChecks(log.Loggable):
logCategory = 'UPnT_ClientEventMessageChecks'
def __init__(self, service):
def __init__(self, service, config):
self.notify_seq = {}
self._service = service
self._xmlschema = None
schema_file_dir = config.get('schema_file_dir', None)
if schema_file_dir is not None:
xmlschema_doc = etree.parse(schema_file_dir + 'event_notify.xsd')
self._xmlschema = etree.XMLSchema(xmlschema_doc)
self._host = ''
self.isHostAvailable()
......@@ -31,15 +42,20 @@ class ServerEventMessageChecks(log.Loggable):
else:
self._host = self._service.parentDevice.host.ip
louie.connect(self.checkEventMessage, 'UPnT.event.message_received', louie.Any, weak=False)
self._control_point = ControlPoint(self._service.parentDevice.host._coherence)
self._event_Server = event.EventServer(self._control_point)
event.subscribe(self._service)
self.debug('ClientEventMessageCheck initialized for service %r' % str(self._service))
def checkEventMessage(self, packet_data):
self.debug('checkEventMessage (%r)' % packet_data)
[command, header, body] = splitData(packet_data)
if command['method'] == 'notify':
self.checkNotifyMessage(command, header, body)
def checkEventMessage(self, command, header, data):
#self.debug('checkEventMessage (%r)' % packet_data)
self.debug('checkEventMessage')
if command['method'].lower() == 'notify':
self.checkNotifyMessage(command, header, data)
else:
self.debug(packet_data)
self.debug(command)
self.debug(header)
self.debug(data)
def calcNextEventSeq(self, service_host, service_url, service_sid):
"""
......@@ -191,35 +207,28 @@ class ServerEventMessageChecks(log.Loggable):
[command['path'], header['host']]
)
# TIMEOUT header check
if not header.has_key('timeout'):
louie.send(
'UPnT.infoMessage',
None,
'A subscribe message should include a TIMEOUT header (%s)' % command['path']
)
self.validateMessage(body, header['host'] + command['path'])
def validateMessage(self, message, service_info):
"""
Message will be checked if its XML is well-formed.
"""
self.info('validateMessage')
doc = etree.parse(StringIO(message.strip()))
if self._xmlschema is None:
louie.send('UPnT.infoMessage', None, 'No XML Schema to compare description against!')
elif self._xmlschema.validate(doc) == 1:
louie.send('UPnT.infoMessage', None, 'Body of notify message valid! (%s)' % service_info)
else:
value = header['timeout'].split('-')
if value[0].strip() == 'Second':
try:
i = int(value[1].strip())
except ValueError:
if value[1].strip() != 'infinite':
louie.send(
'UPnT.event.notify_incorrect',
None,
'Notify: Wrong value for TIMEOUT header',
header['timeout'],
[command['path'], header['host']]
)
else:
louie.send(
'UPnT.event.notify_incorrect',
None,
'Notify: Wrong value for TIMEOUT header',
header['timeout'],
[command['path'], header['host']]
)
error = self._xmlschema.error_log.last_error
self.warning(error)
louie.send('UPnT.infoMessage', None, 'Body of notify message not valid! (%s)' % service_info)
self.debug('Message:\n%s' % message)
class ClientEventMessageChecks(log.Loggable):
......@@ -246,14 +255,9 @@ class ClientEventMessageChecks(log.Loggable):
self.debug('ServerEventMessageCheck initialized for service %r' % str(self._service))
louie.connect(self.checkEventMessage, 'UPnT.event.message_received', louie.Any, weak=False)
def checkEventMessage(self, packet_data):
self.debug('checkEventMessage (%r)' % packet_data)
[command, header, body] = splitData(packet_data)
host = header['host'].split(':')[0].strip()
#if not (command['path'] == self._service.controlUrl and \
# host ==
def checkEventMessage(self, command, header, data):
self.debug('checkEventMessage')
if command['method'] == 'unsubscribe':
self.checkUnsubscribeMessage(command, header, body)
......@@ -479,18 +483,3 @@ class ClientEventMessageChecks(log.Loggable):
[command['path'], header['host']]
)
def splitData(self, packet_data):
[header, body] = packet_data.split('\r\n\r\n', 1)
lines = header.split('\r\n')
#split first line to get the command, requested resource and protocol version
cmd = string.split(lines[0], ' ')
command = {'method': cmd[0].lower(), 'path': cmd[1].lower(), 'protocol': cmd[2].lower()}
#format all lines but the first and transform it into a dictionary
lines = map(lambda x: x.replace(': ', ':', 1), lines[1:])
lines = filter(lambda x: len(x) > 0, lines)
headers = [string.split(x, ':', 1) for x in lines]
headers = dict(map(lambda x: (x[0].lower().strip(), x[1].strip()), headers))
return [command, headers, body]
......@@ -23,7 +23,7 @@ class Host(log.Loggable):
logCategory = 'UPnT_Host'
def __init__(self, ip, config):
def __init__(self, ip, config, coherence):
self._config = config
......@@ -31,6 +31,7 @@ class Host(log.Loggable):
self._devices = {}
self._services = {}
self._create_dev_tree_func = None
self._coherence = coherence
def getIp(self):
return self._ip
......
......@@ -10,6 +10,8 @@ from StringIO import StringIO
import louie
import urllib2
from coherence.upnp.core import utils
from coherence import log
......@@ -31,6 +33,7 @@ class ServerDevice(log.Loggable):
self._device_type = ''
self._device_version = ''
self._desc_location = ''
self._url_base = ''
self._sub_devices = {}
self._services = {}
......@@ -97,6 +100,10 @@ class ServerDevice(log.Loggable):
self._desc_location = headers['location']
parsed = urllib2.urlparse.urlparse(self._desc_location)
self.url_base = "%s://%s" % (parsed[0], parsed[1])
if len(usn_split) > 1:
usn_right_parts = usn_split[1].split(':')
......@@ -106,6 +113,8 @@ class ServerDevice(log.Loggable):
self._device_version = usn_right_parts[4]
else:
self._root_device = True
self.debug('%r' % self)
def addSubdevice(self, subdevice):
"""Add a subdevice"""
......
......@@ -36,8 +36,12 @@ class Service(log.Loggable):
self._SCPDURL = ''
self._controlURL = ''
self._eventSubURL = ''
self._url_base = ''
self._expiration_notification_func = None
self._action_list = {}
self._event_list = {}
self._xmlschema = None
schema_file_dir = config.get('schema_file_dir', None)
if schema_file_dir is not None:
......@@ -47,7 +51,9 @@ class Service(log.Loggable):
self.update(headers, True)
self.eventchecker = ServerEventMessageChecks(self)
self.eventchecker = ServerEventMessageChecks(self, config)
self.event_connection = None
self._subscription_id = ''
def __str__(self):
return self.parentDevice.UUID + '::' + self.serviceType
......@@ -79,7 +85,15 @@ class Service(log.Loggable):
def setControlUrl(self, controlUrl):
self._controlURL = controlUrl
controlUrl = property(getControlUrl, setControlUrl)
# interface compatibility functions
def get_base_url(self):
return self._url_base
def get_event_sub_url(self):
return self._eventSubURL
def get_sid(self):
return self._subscription_id
def checkDescriptions(self):
"""Check if description of service is valid"""
......@@ -102,12 +116,14 @@ class Service(log.Loggable):
d.addErrback(self.validateDescriptionContentFailed, self._url_base + self._SCPDURL, d=d)
def validateDescription(self, desc):
"""Description was sucessfully received and will be checked"""
"""
Description was sucessfully received and will be checked if its XML is
well-formed.
"""
self.info('validateDescription')
description, headers = desc
#description = self.removeUnknownTags(description)
doc = etree.parse(StringIO(description))
if self._xmlschema is None:
......@@ -170,7 +186,11 @@ class Service(log.Loggable):
raise MissingRequiredStateVariableException(statevar_name, self.parentDevice.UUID + '::' + self.serviceType)
def checkActionSyntax(self, template_action_list, description_action_list, ns):
"""Check, if all implemented actions are syntactically correct."""
"""
Check, if all implemented actions are syntactically correct. While being
at it, store all actions in a dictionary to be able to call all the
actions for verifying control behaviour.
"""
self.info('checkActionSyntax')
......@@ -359,14 +379,16 @@ class Service(log.Loggable):
return True
def validateDescriptionContent(self, doc, d):
"""Run tests on service description"""
"""
Now that the service description is well-formed, check the contents of it.
"""
self.info('validateDescriptionContent')
template_file = self.templates_dir + 'service/' + self._serviceType + self._serviceVersion + '.xml'
if not path.isfile(template_file):
self.debug('no service template available (%s)' % template_file)
return doc
raise MissingTemplateException('no service template available (%s)' % template_file)
ns = {'upnp': 'urn:schemas-upnp-org:service-1-0'}
service_template = etree.parse(template_file)
......@@ -416,12 +438,6 @@ class Service(log.Loggable):
louie.send('UPnT.ending_deferred', None, d)
def checkControlAvailability(self):
"""Issue a standard state variable retrival request to check for control availability"""
d = utils.getPage(self._url_base + self._SCPDURL)
def update(self, headers, new=False):
"""Update this service with information from the ssdp:alive paket"""
......@@ -451,6 +467,7 @@ class Service(log.Loggable):
self._expiration_notification_func = None
louie.send('UPnT.infoMessage', None, 'Service announcement of %s:%s has expired!' % (self._serviceType, self._serviceVersion))
class MissingUrlException(StandardError):
"""Error representing a missing URL"""
def __init__(self, error):
......@@ -459,6 +476,7 @@ class MissingUrlException(StandardError):
def __str__(self):
return repr(self.error)
class MissingRequiredActionException(StandardError):
"""Error representing a missing action in a service description"""
def __init__(self, missingAction, deviceName):
......@@ -468,6 +486,7 @@ class MissingRequiredActionException(StandardError):
def __str__(self):
return 'Required action %s not implemented by %s' % (self.missingAction, self.deviceName)
class SyntaxError(StandardError):
"""Error representing wrong syntax in action description"""
def __init__(self, message, actionName, deviceName):
......@@ -478,6 +497,7 @@ class SyntaxError(StandardError):
def __str__(self):
return '%s (action %s, device %s)' % (self.message, self.actionName, self.deviceName)
class MissingRequiredStateVariableException(StandardError):
"""Error representing a missing state variable in a service description"""
def __init__(self, missingVariable, deviceName):
......@@ -487,3 +507,11 @@ class MissingRequiredStateVariableException(StandardError):
def __str__(self):