Commit 415080c0 authored by Michael Weinrich's avatar Michael Weinrich
Browse files

eventing.py documented

Minor code changes
parent 01764a03
# Licensed under the MIT license """
# http://opensource.org/licenses/mit-license.php Module that holds the classes for checking event messages sent by client or
server devices.
# Copyright 2007, Michael Weinrich <testsuite@michael-weinrich.de> @license: Licensed under the MIT license
http://opensource.org/licenses/mit-license.php
@author: Michael Weinrich <testsuite@michael-weinrich.de>
@copyright: Copyright 2007, Michael Weinrich
"""
from coherence import log # external includes
from coherence.upnp.core import event
from coherence.upnp.devices.control_point import ControlPoint
import urllib2
import louie import louie
import urllib2
from lxml import etree from lxml import etree
from twisted.internet import reactor
from StringIO import StringIO from StringIO import StringIO
from twisted.internet import reactor
# Coherence includes
from coherence import log
from coherence.upnp.core import event
from coherence.upnp.devices.control_point import ControlPoint
global control_point, event_server global control_point, event_server
control_point = None control_point = None
"""Global variable for a single instance of a C{ControlPoint}."""
event_server = None event_server = None
"""Global variable for a single instance of an C{EventServer}."""
class ServerEventMessageChecks(log.Loggable): class ServerEventMessageChecks(log.Loggable):
""" """
This class contains event message checks for client devices like This class contains event message checks for server devices like
MediaServer and MediaRenderer for instance. This way this class checks MediaServer and MediaRenderer for instance. It runs as part of a virtual
messages sent by a control point for validity. client device (like a control point) and checks notify messages for
validity.
The main task is to listen to the C{UPnT.event.server_message_received}
signal and process the data given by this signal.
""" """
logCategory = 'UPnT_ServerEventMessageChecks' logCategory = 'UPnT_ServerEventMessageChecks'
"""Log category for the server event checker."""
def __init__(self, service, config): def __init__(self, service, config):
"""
Initialise the server event messsage checker.
@param service: The service where this checker will subscribe for event
messages.
@param config: A C{dict} containing the configuration data from the
config file.
"""
self.notifySeq = {} self.notifySeq = {}
"""Storage for sequence numbers to enable tracking of sequence numbers
in event messages."""
self._service = service self._service = service
"""The service whose event messages will be checked."""
self._xmlschema = None self._xmlschema = None
"""An XML schema that is used to validate the content of an event
message."""
schema_file_dir = config.get('schema_file_dir', None) schema_file_dir = config.get('schema_file_dir', None)
if schema_file_dir is not None: if schema_file_dir is not None:
xmlschema_doc = etree.parse(schema_file_dir + 'event_notify.xsd') xmlschema_doc = etree.parse(schema_file_dir + 'event_notify.xsd')
self._xmlschema = etree.XMLSchema(xmlschema_doc) self._xmlschema = etree.XMLSchema(xmlschema_doc)
self._host = '' self._host = ''
"""The host where the observed event server is running."""
self._renew_func = None self._renew_func = None
"""An object which provides C{IDelayedCall} for L{renewSubscription}
which renews the subscription at the event server every 290 seconds."""
self.isHostAvailable() self.isHostAvailable()
def isHostAvailable(self): def isHostAvailable(self):
"""
Check if the parent device is already available. If not, try again in
two seconds. If so, get the host IP, connect the signal to the
processing function, get the control point and event server and
subscribe for event messages.
"""
if self._service.parentDevice is None: if self._service.parentDevice is None:
reactor.callLater(2, self.isHostAvailable) reactor.callLater(2, self.isHostAvailable)
else: else:
...@@ -48,40 +84,70 @@ class ServerEventMessageChecks(log.Loggable): ...@@ -48,40 +84,70 @@ class ServerEventMessageChecks(log.Loggable):
louie.connect(self.checkEventMessage, 'UPnT.event.server_message_received', louie.Any, weak=False) louie.connect(self.checkEventMessage, 'UPnT.event.server_message_received', louie.Any, weak=False)
global control_point, event_server global control_point, event_server
if not control_point: if not control_point:
control_point = ControlPoint(self._service.parentDevice.host._coherence) if not self._service.parentDevice.host._coherence.ctrl:
print 'ControlPoint erstellt' control_point = ControlPoint(self._service.parentDevice.host._coherence)
else:
control_point = self._service.parentDevice.host._coherence.ctrl
self.debug('ControlPoint created')
if not event_server: if not event_server:
event_server = event.EventServer(control_point) event_server = event.EventServer(control_point)
print 'EventServer erstellt' self.debug('EventServer erstellt')
self.renewSubscription() self.renewSubscription()
self.debug('ServerEventMessageChecks initialized for service %r' % str(self._service)) self.debug('ServerEventMessageChecks initialized for service %r' % str(self._service))
def renewSubscription(self): def renewSubscription(self):
"""
Renew the subscription for event messages and schedule the next renewal
in 290 seconds.
"""
event.subscribe(self._service) event.subscribe(self._service)
self._renew_func = reactor.callLater(290, self.renewSubscription) self._renew_func = reactor.callLater(290, self.renewSubscription)
def cancelSubscription(self): def cancelSubscription(self):
"""
Cancel the subscription for event messages and also cancel the scheduled
renewal.
"""
if self._renew_func.active(): if self._renew_func.active():
self._renew_func.cancel() self._renew_func.cancel()
event.unsubscribe(self._service) event.unsubscribe(self._service)
def checkEventMessage(self, command, header, data): def checkEventMessage(self, command, header, body):
"""
Processing function that is called when a
C{UPnT.event.server_message_received} signal is received.
If the command in the event packet is known, the L{checkNotifyMessage}
function is called. Otherwise the erroneous packet will be displayed.
@param command: A C{dict} for the command line of the event packet with
keys 'method', 'resource' and 'protocol'.
@param header: A C{dict} containing all headers from the event packet
except the command line.
@param body: C{str} containing the body of the event packet.
"""
#self.debug('checkEventMessage (%r)' % packet_data) #self.debug('checkEventMessage (%r)' % packet_data)
self.debug('checkEventMessage') self.debug('checkEventMessage')
if command['method'].lower() == 'notify': if command['method'].lower() == 'notify':
self.checkNotifyMessage(command, header, data) self.checkNotifyMessage(command, header, body)
else: else:
self.debug(command) self.debug(command)
self.debug(header) self.debug(header)
self.debug(data) self.debug(body)
def calcNextEventSeq(self, service_host, service_url, service_sid): def calcNextEventSeq(self, service_ident, service_sid):
""" """
Check for the next sequence id. If none is stored, create a new entry Check for the next sequence id. If none is stored, create a new entry
and start counting at 0. Return the new value. and start counting at 0. If there is already one stored, increment it
and wrap it to 1 if it exceeded the maximum value of 429496729.
@param service_ident: A unique identification that consists of the host
IP and the path of the event server.
@param service_sid: The unique identifier of the subscription.
@return: The new sequence value (C{int}).
""" """
self.debug('calcNextEventSeq') self.debug('calcNextEventSeq')
service_ident = service_host + service_url
# check for dict index # check for dict index
if not self.notifySeq.has_key(service_ident): if not self.notifySeq.has_key(service_ident):
...@@ -97,18 +163,26 @@ class ServerEventMessageChecks(log.Loggable): ...@@ -97,18 +163,26 @@ class ServerEventMessageChecks(log.Loggable):
#self.debug('calcNextEventSeq (%r, %r, %r, %r)' % (service_host, service_url, service_sid, self.notifySeq[service_ident][service_sid])) #self.debug('calcNextEventSeq (%r, %r, %r, %r)' % (service_host, service_url, service_sid, self.notifySeq[service_ident][service_sid]))
def removeEventSeq(self, service): def removeEventSeq(self):
""" """
Remove the sequence number from the dictionary so that it starts again Remove the sequence number from the dictionary so that it starts again
at 0 when some device subscribes the next time at 0 when we subscribe to event messages of a device the next time.
""" """
#self.debug('removeEventSeq (%r)' % self.notifySeq)
#if service.scpdUrl == self._service.scpdUrl: self.debug('removeEventSeq (%r)' % self.notifySeq)
#del self.notify_seq[self._host + self._service.event] del self.notify_seq[self._host + self._service.event]
def checkNotifyMessage(self, command, header, body): def checkNotifyMessage(self, command, header, body):
"""
Check the headers of the event message. The body is checked in the
L{validateMessage} function which is called at the end of this one.
@param command: A C{dict} for the command line of the event packet with
keys 'method', 'resource' and 'protocol'.
@param header: A C{dict} containing all headers from the event packet
except the command line.
@param body: C{str} containing the body of the event packet.
"""
self.debug('checkNotifyMessage') self.debug('checkNotifyMessage')
#self.debug(header) #self.debug(header)
#self.debug(body) #self.debug(body)
...@@ -219,8 +293,8 @@ class ServerEventMessageChecks(log.Loggable): ...@@ -219,8 +293,8 @@ class ServerEventMessageChecks(log.Loggable):
[header['host'], command['path']] [header['host'], command['path']]
) )
else: else:
self.calcNextEventSeq(header['host'], command['path'], header['sid'])
service_ident = header['host'] + command['path'] service_ident = header['host'] + command['path']
self.calcNextEventSeq(service_ident, header['sid'])
if header['seq'] != self.notifySeq[service_ident][header['sid']]: if header['seq'] != self.notifySeq[service_ident][header['sid']]:
louie.send( louie.send(
'UPnT.event.notify_incorrect', 'UPnT.event.notify_incorrect',
...@@ -229,11 +303,15 @@ class ServerEventMessageChecks(log.Loggable): ...@@ -229,11 +303,15 @@ class ServerEventMessageChecks(log.Loggable):
[header['host'], command['path']] [header['host'], command['path']]
) )
self.validateMessage(body, header['host'] + command['path']) self.validateMessage(body, header['host'], command['path'])
def validateMessage(self, message, service_info): def validateMessage(self, message, header_host, command_path):
""" """
Message will be checked if its XML is well-formed. The event message will be checked if its XML is well-formed.
@param message: The body of the event packet.
@param header_host: The host field of the header of the packet.
@param command_path: The path of the event server.
""" """
self.info('validateMessage') self.info('validateMessage')
...@@ -250,25 +328,51 @@ class ServerEventMessageChecks(log.Loggable): ...@@ -250,25 +328,51 @@ class ServerEventMessageChecks(log.Loggable):
else: else:
error = self._xmlschema.error_log.last_error error = self._xmlschema.error_log.last_error
self.warning(error) self.warning(error)
louie.send('UPnT.infoMessage', None, 'Body of notify message not valid! (%s)' % service_info) louie.send(
'UPnT.event.notify_incorrect',
None,
"Body of notify message not valid! (%s)" % service_ident,
[header_host, command_path]
)
louie.send('UPnT.infoMessage', None, 'Body of notify message not valid! (%s%s)' % (header_host, command_path))
self.debug('Message:\n%s' % message) self.debug('Message:\n%s' % message)
class ClientEventMessageChecks(log.Loggable): class ClientEventMessageChecks(log.Loggable):
""" """
This class contains event message checks for client devices, mainly the This class contains event message checks for client devices, mainly the
control point kind of devices. This way this class checks messages sent by a control point kind of devices. It runs as part of a virtual server device
server device like a MediaServer for instance. (like a MediaServer) and checks message sent by a client device.
The main task is to listen to the C{UPnT.event.client_message_received}
signal and process the data given by this signal.
""" """
logCategory = 'UPnT_ClientEventMessageChecks' logCategory = 'UPnT_ClientEventMessageChecks'
"""Log category for the client event checker."""
def __init__(self): def __init__(self):
"""
Initialise the server event messsage checker and connect to the
C{UPnT.event.client_message_received} signal.
"""
louie.connect(self.checkEventMessage, 'UPnT.event.client_message_received', louie.Any, weak=False) louie.connect(self.checkEventMessage, 'UPnT.event.client_message_received', louie.Any, weak=False)
self.debug('ClientEventMessageChecks initialized') self.debug('ClientEventMessageChecks initialized')
def checkEventMessage(self, command, header, body): def checkEventMessage(self, command, header, body):
"""
Processing function that is called when a
C{UPnT.event.client_message_received} signal is received.
If the command in the event packet is known, the according checking
function is called. Otherwise the erroneous packet will be displayed.
@param command: A C{dict} for the command line of the event packet with
keys 'method', 'resource' and 'protocol'.
@param header: A C{dict} containing all headers from the event packet
except the command line.
@param body: C{str} containing the body of the event packet.
"""
self.debug('checkEventMessage') self.debug('checkEventMessage')
#print command #print command
#print header #print header
...@@ -287,6 +391,17 @@ class ClientEventMessageChecks(log.Loggable): ...@@ -287,6 +391,17 @@ class ClientEventMessageChecks(log.Loggable):
self.debug(body) self.debug(body)
def checkSubscriptionMessage(self, command, header, body): def checkSubscriptionMessage(self, command, header, body):
"""
A subscription request was received and the headers of this packet are
checked for validity.
@param command: A C{dict} for the command line of the event packet with
keys 'method', 'resource' and 'protocol'.
@param header: A C{dict} containing all headers from the event packet
except the command line.
@param body: C{str} containing the body of the event packet which has
to be empty.
"""
self.debug('checkSubscriptionMessage') self.debug('checkSubscriptionMessage')
# HOST header check # HOST header check
...@@ -371,7 +486,17 @@ class ClientEventMessageChecks(log.Loggable): ...@@ -371,7 +486,17 @@ class ClientEventMessageChecks(log.Loggable):
self.debug('checkSubscriptionMessage-Finished') self.debug('checkSubscriptionMessage-Finished')
def checkRenewalMessage(self, command, header, body): def checkRenewalMessage(self, command, header, body):
"""
A subscription renewal was received and the headers of this packet are
checked for validity.
@param command: A C{dict} for the command line of the event packet with
keys 'method', 'resource' and 'protocol'.
@param header: A C{dict} containing all headers from the event packet
except the command line.
@param body: C{str} containing the body of the event packet which has
to be empty.
"""
self.debug('checkRenewalMessage') self.debug('checkRenewalMessage')
# HOST header check # HOST header check
if not header.has_key('host'): if not header.has_key('host'):
...@@ -433,22 +558,40 @@ class ClientEventMessageChecks(log.Loggable): ...@@ -433,22 +558,40 @@ class ClientEventMessageChecks(log.Loggable):
louie.send( louie.send(
'UPnT.event.renewal_incorrect', 'UPnT.event.renewal_incorrect',
None, None,
'Renewal: Wrong value for TIMEOUT header', 'Renewal: Wrong value for TIMEOUT header (%r)' % header['timeout'],
header['timeout'],
[header['host'], command['path']] [header['host'], command['path']]
) )
else: else:
louie.send( louie.send(
'UPnT.event.renewal_incorrect', 'UPnT.event.renewal_incorrect',
None, None,
'Wrong value for TIMEOUT header', 'Renewal: Wrong value for TIMEOUT header (%r)' % header['timeout'],
header['timeout'],
[header['host'], command['path']] [header['host'], command['path']]
) )
# message shouldn't have body content
if len(body) > 0:
louie.send(
'UPnT.event.renewal_incorrect',
None,
'Renewal: Conctent found in body (%r)' % body,
[header['host'], command['path']]
)
self.debug('checkRenewalMessage-Finished') self.debug('checkRenewalMessage-Finished')
def checkUnsubscribeMessage(self, command, header, body): def checkUnsubscribeMessage(self, command, header, body):
"""
A unsubscribe request was received and the headers of this packet are
checked for validity.
@param command: A C{dict} for the command line of the event packet with
keys 'method', 'resource' and 'protocol'.
@param header: A C{dict} containing all headers from the event packet
except the command line.
@param body: C{str} containing the body of the event packet which has
to be empty.
"""
self.debug('checkUnsubscribeMessage') self.debug('checkUnsubscribeMessage')
# HOST header check # HOST header check
if not header.has_key('host'): if not header.has_key('host'):
...@@ -501,5 +644,14 @@ class ClientEventMessageChecks(log.Loggable): ...@@ -501,5 +644,14 @@ class ClientEventMessageChecks(log.Loggable):
'Unsubscribe: TIMEOUT header header found, not allowed for unsubscribe message', 'Unsubscribe: TIMEOUT header header found, not allowed for unsubscribe message',
[header['host'], command['path']] [header['host'], command['path']]
) )
# message shouldn't have body content
if len(body) > 0:
louie.send(
'UPnT.event.unsubscribe_incorrect',
None,
'Renewal: Conctent found in body (%r)' % body,
[header['host'], command['path']]
)
self.debug('checkUnsubscribeMessage-Finished') self.debug('checkUnsubscribeMessage-Finished')
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment