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
from coherence.upnp.core import event
from coherence.upnp.devices.control_point import ControlPoint
import urllib2
# external includes
import louie
import urllib2
from lxml import etree
from twisted.internet import reactor
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
control_point = None
"""Global variable for a single instance of a C{ControlPoint}."""
event_server = None
"""Global variable for a single instance of an C{EventServer}."""
class ServerEventMessageChecks(log.Loggable):
"""
This class contains event message checks for client devices like
MediaServer and MediaRenderer for instance. This way this class checks
messages sent by a control point for validity.
This class contains event message checks for server devices like
MediaServer and MediaRenderer for instance. It runs as part of a virtual
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'
"""Log category for the server event checker."""
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 = {}
"""Storage for sequence numbers to enable tracking of sequence numbers
in event messages."""
self._service = service
"""The service whose event messages will be checked."""
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)
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 = ''
"""The host where the observed event server is running."""
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()
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:
reactor.callLater(2, self.isHostAvailable)
else:
......@@ -48,40 +84,70 @@ class ServerEventMessageChecks(log.Loggable):
louie.connect(self.checkEventMessage, 'UPnT.event.server_message_received', louie.Any, weak=False)
global control_point, event_server
if not control_point:
control_point = ControlPoint(self._service.parentDevice.host._coherence)
print 'ControlPoint erstellt'
if not self._service.parentDevice.host._coherence.ctrl:
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:
event_server = event.EventServer(control_point)
print 'EventServer erstellt'
self.debug('EventServer erstellt')
self.renewSubscription()
self.debug('ServerEventMessageChecks initialized for service %r' % str(self._service))
def renewSubscription(self):
"""
Renew the subscription for event messages and schedule the next renewal
in 290 seconds.
"""
event.subscribe(self._service)
self._renew_func = reactor.callLater(290, self.renewSubscription)
def cancelSubscription(self):
"""
Cancel the subscription for event messages and also cancel the scheduled
renewal.
"""
if self._renew_func.active():
self._renew_func.cancel()
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')
if command['method'].lower() == 'notify':
self.checkNotifyMessage(command, header, data)
self.checkNotifyMessage(command, header, body)
else:
self.debug(command)
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
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')
service_ident = service_host + service_url
# check for dict index
if not self.notifySeq.has_key(service_ident):
......@@ -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]))
def removeEventSeq(self, service):
def removeEventSeq(self):
"""
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:
#del self.notify_seq[self._host + self._service.event]
self.debug('removeEventSeq (%r)' % self.notifySeq)
del self.notify_seq[self._host + self._service.event]
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(header)
#self.debug(body)
......@@ -219,8 +293,8 @@ class ServerEventMessageChecks(log.Loggable):
[header['host'], command['path']]
)
else:
self.calcNextEventSeq(header['host'], command['path'], header['sid'])
service_ident = header['host'] + command['path']
self.calcNextEventSeq(service_ident, header['sid'])
if header['seq'] != self.notifySeq[service_ident][header['sid']]:
louie.send(
'UPnT.event.notify_incorrect',
......@@ -229,11 +303,15 @@ class ServerEventMessageChecks(log.Loggable):
[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')
......@@ -250,25 +328,51 @@ class ServerEventMessageChecks(log.Loggable):
else:
error = self._xmlschema.error_log.last_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)
class ClientEventMessageChecks(log.Loggable):
"""
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
server device like a MediaServer for instance.
control point kind of devices. It runs as part of a virtual server device
(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'
"""Log category for the client event checker."""
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)
self.debug('ClientEventMessageChecks initialized')
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')
#print command
#print header
......@@ -287,6 +391,17 @@ class ClientEventMessageChecks(log.Loggable):
self.debug(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')
# HOST header check
......@@ -371,7 +486,17 @@ class ClientEventMessageChecks(log.Loggable):
self.debug('checkSubscriptionMessage-Finished')
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')
# HOST header check
if not header.has_key('host'):
......@@ -433,22 +558,40 @@ class ClientEventMessageChecks(log.Loggable):
louie.send(
'UPnT.event.renewal_incorrect',
None,
'Renewal: Wrong value for TIMEOUT header',
header['timeout'],
'Renewal: Wrong value for TIMEOUT header (%r)' % header['timeout'],
[header['host'], command['path']]
)
else:
louie.send(
'UPnT.event.renewal_incorrect',
None,
'Wrong value for TIMEOUT header',
header['timeout'],
'Renewal: Wrong value for TIMEOUT header (%r)' % header['timeout'],
[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')
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')
# HOST header check
if not header.has_key('host'):
......@@ -501,5 +644,14 @@ class ClientEventMessageChecks(log.Loggable):
'Unsubscribe: TIMEOUT header header found, not allowed for unsubscribe message',
[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')
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