Skip to content

Commit

Permalink
Add files via upload
Browse files Browse the repository at this point in the history
  • Loading branch information
philippeportesppo authored Feb 12, 2018
1 parent 91f90f5 commit 7fc6d7d
Show file tree
Hide file tree
Showing 2 changed files with 362 additions and 0 deletions.
229 changes: 229 additions & 0 deletions ssdp_upnp/lib/ssdp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
# Licensed under the MIT license
# http://opensource.org/licenses/mit-license.php

# Copyright 2005, Tim Potter <[email protected]>
# Copyright 2006 John-Mark Gurney <[email protected]>
# Copyright (C) 2006 Fluendo, S.A. (www.fluendo.com).
# Copyright 2006,2007,2008,2009 Frank Scholz <[email protected]>
# Copyright 2016 Erwan Martin <[email protected]>
#
# Implementation of a SSDP server.
#

import random
import time
import socket
import logging
from email.utils import formatdate
from errno import ENOPROTOOPT

SSDP_PORT = 1900
SSDP_ADDR = '239.255.255.250'
SERVER_ID = 'Raspberry Pi SSDP Server'


#logger = logging.getLogger()


class SSDPServer:
"""A class implementing a SSDP server. The notify_received and
searchReceived methods are called when the appropriate type of
datagram is received by the server."""
known = {}

def __init__(self):
self.sock = None

def run(self):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
if hasattr(socket, "SO_REUSEPORT"):
try:
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
except socket.error as le:
# RHEL6 defines SO_REUSEPORT but it doesn't work
if le.errno == ENOPROTOOPT:
pass
else:
raise

addr = socket.inet_aton(SSDP_ADDR)
interface = socket.inet_aton('0.0.0.0')
cmd = socket.IP_ADD_MEMBERSHIP
self.sock.setsockopt(socket.IPPROTO_IP, cmd, addr + interface)
self.sock.bind(('0.0.0.0', SSDP_PORT))
self.sock.settimeout(1)

while True:
try:
data, addr = self.sock.recvfrom(1024)
self.datagram_received(data, addr)
except socket.timeout:
continue
self.shutdown()

def shutdown(self):
for st in self.known:
if self.known[st]['MANIFESTATION'] == 'local':
self.do_byebye(st)

def datagram_received(self, data, host_port):
"""Handle a received multicast datagram."""

(host, port) = host_port

try:
header, payload = data.decode().split('\r\n\r\n')[:2]
except ValueError as err:
print(err)
return

lines = header.split('\r\n')
cmd = lines[0].split(' ')
lines = map(lambda x: x.replace(': ', ':', 1), lines[1:])
lines = filter(lambda x: len(x) > 0, lines)

headers = [x.split(':', 1) for x in lines]
headers = dict(map(lambda x: (x[0].lower(), x[1]), headers))

print('SSDP command %s %s - from %s:%d' % (cmd[0], cmd[1], host, port))
#print('with headers: {}.'.format(headers))
if cmd[0] == 'M-SEARCH' and cmd[1] == '*':
print ('SSDP discovery')
self.discovery_request(headers, (host, port))
elif cmd[0] == 'NOTIFY' and cmd[1] == '*':
# SSDP presence
print('NOTIFY *')
else:
print('Unknown SSDP command %s %s' % (cmd[0], cmd[1]))

def register(self, manifestation, usn, st, location, server=SERVER_ID, cache_control='max-age=1800', silent=False,
host=None):
"""Register a service or device that this SSDP server will
respond to."""

print('Registering %s (%s)' % (st, location))

self.known[usn] = {}
self.known[usn]['USN'] = usn
self.known[usn]['LOCATION'] = location
self.known[usn]['ST'] = st
self.known[usn]['EXT'] = ''
self.known[usn]['SERVER'] = server
self.known[usn]['CACHE-CONTROL'] = cache_control

self.known[usn]['MANIFESTATION'] = manifestation
self.known[usn]['SILENT'] = silent
self.known[usn]['HOST'] = host
self.known[usn]['last-seen'] = time.time()

if manifestation == 'local' and self.sock:
self.do_notify(usn)

def unregister(self, usn):
print("Un-registering %s" % usn)
del self.known[usn]

def is_known(self, usn):
return usn in self.known

def send_it(self, response, destination, delay, usn):
#print('send discovery response delayed by %ds for %s to %r' % (delay, usn, destination))
try:
self.sock.sendto(response.encode(), destination)
except (AttributeError, socket.error) as msg:
print("failure sending out byebye notification: %r" % msg)

def discovery_request(self, headers, host_port):
"""Process a discovery request. The response must be sent to
the address specified by (host, port)."""

(host, port) = host_port

print('Discovery request from (%s,%d) for %s' % (host, port, headers['st']))
if headers['st'] == "urn:schemas-upnp-org:device:AirMentorPro2:1":
print('Discovery request for %s' % headers['st'])
print self.known.values()

# Do we know about this service?
for i in self.known.values():
if i['MANIFESTATION'] == 'remote':
continue
if headers['st'] == 'ssdp:all' and i['SILENT']:
continue
if i['ST'] == headers['st'] or headers['st'] == 'ssdp:all':
print "HTTP/1.1 200 OK"
response = ['HTTP/1.1 200 OK']

usn = None
for k, v in i.items():
if k == 'USN':
usn = v
if k not in ('MANIFESTATION', 'SILENT', 'HOST'):
response.append('%s: %s' % (k, v))

if usn:
response.append('DATE: %s' % formatdate(timeval=None, localtime=False, usegmt=True))

response.extend(('', ''))
delay = random.randint(0, int(headers['mx']))

self.send_it('\r\n'.join(response), (host, port), delay, usn)

def do_notify(self, usn):
"""Do notification"""

if self.known[usn]['SILENT']:
return
# print('Sending alive notification for %s' % usn)

resp = [
'NOTIFY * HTTP/1.1',
'HOST: %s:%d' % (SSDP_ADDR, SSDP_PORT),
'NTS: ssdp:alive',
]
stcpy = dict(self.known[usn].items())
stcpy['NT'] = stcpy['ST']
del stcpy['ST']
del stcpy['MANIFESTATION']
del stcpy['SILENT']
del stcpy['HOST']
del stcpy['last-seen']

resp.extend(map(lambda x: ': '.join(x), stcpy.items()))
resp.extend(('', ''))
# print('do_notify content', resp)
try:
self.sock.sendto('\r\n'.join(resp).encode(), (SSDP_ADDR, SSDP_PORT))
self.sock.sendto('\r\n'.join(resp).encode(), (SSDP_ADDR, SSDP_PORT))
except (AttributeError, socket.error) as msg:
print("failure sending out alive notification: %r" % msg)

def do_byebye(self, usn):
"""Do byebye"""

# print('Sending byebye notification for %s' % usn)

resp = [
'NOTIFY * HTTP/1.1',
'HOST: %s:%d' % (SSDP_ADDR, SSDP_PORT),
'NTS: ssdp:byebye',
]
try:
stcpy = dict(self.known[usn].items())
stcpy['NT'] = stcpy['ST']
del stcpy['ST']
del stcpy['MANIFESTATION']
del stcpy['SILENT']
del stcpy['HOST']
del stcpy['last-seen']
resp.extend(map(lambda x: ': '.join(x), stcpy.items()))
resp.extend(('', ''))
# print('do_byebye content', resp)
if self.sock:
try:
self.sock.sendto('\r\n'.join(resp), (SSDP_ADDR, SSDP_PORT))
except (AttributeError, socket.error) as msg:
print("failure sending out byebye notification: %r" % msg)
except KeyError as msg:
print("error building byebye notification: %r" % msg)
133 changes: 133 additions & 0 deletions ssdp_upnp/lib/upnp_http_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@

from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import threading

PORT_NUMBER = 8090


class UPNPHTTPServerHandler(BaseHTTPRequestHandler):
"""
A HTTP handler that serves the UPnP XML files.
"""

# Handler for the GET requests
def do_GET(self):
print "===============",self.path

if self.path == '/airmentor_wsd.xml':
self.send_response(200)
self.send_header('Content-type', 'application/xml')
self.end_headers()
self.wfile.write(self.get_wsd_xml().encode())
return
if self.path == '/airmentorpro2.xml':
self.send_response(200)
self.send_header('Content-type', 'application/xml')
self.end_headers()
self.wfile.write(self.get_device_xml().encode())
return
else:
self.send_response(404)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(b"Not found.")
return

def get_device_xml(self):
"""
Get the main device descriptor xml file.
"""
xml = """<root>
<specVersion>
<major>1</major>
<minor>0</minor>
</specVersion>
<device>
<deviceType>urn:schemas-upnp-org:device:AirMentorPro2:1</deviceType>
<friendlyName>{friendly_name}</friendlyName>
<manufacturer>{manufacturer}</manufacturer>
<manufacturerURL>{manufacturer_url}</manufacturerURL>
<modelDescription>{model_description}</modelDescription>
<modelName>{model_name}</modelName>
<modelNumber>{model_number}</modelNumber>
<modelURL>{model_url}</modelURL>
<serialNumber>{serial_number}</serialNumber>
<UDN>uuid:{uuid}</UDN>
<serviceList>
<service>
<URLBase>https://www.air-mentor.com/web2017/product/air_mentor_8096ap</URLBase>
<serviceType>urn:airmentor:service:AirMentorPro2:1</serviceType>
<serviceId>urn:airmentor:serviceId:AirMentorPro2</serviceId>
<controlURL>/AirMentorPro2</controlURL>
<eventSubURL/>
<SCPDURL>/AirMentorPro2_wsd.xml</SCPDURL>
</service>
</serviceList>
<presentationURL>{presentation_url}</presentationURL>
</device>
</root>"""
return xml.format(friendly_name=self.server.friendly_name,
manufacturer=self.server.manufacturer,
manufacturer_url=self.server.manufacturer_url,
model_description=self.server.model_description,
model_name=self.server.model_name,
model_number=self.server.model_number,
model_url=self.server.model_url,
serial_number=self.server.serial_number,
uuid=self.server.uuid,
presentation_url=self.server.presentation_url)

@staticmethod
def get_wsd_xml():
"""
Get the device WSD file.
"""
return """<scpd xmlns="urn:schemas-upnp-org:service-1-0">
<specVersion>
<major>1</major>
<minor>0</minor>
</specVersion>
</scpd>"""


class UPNPHTTPServerBase(HTTPServer):
"""
A simple HTTP server that knows the information about a UPnP device.
"""
def __init__(self, server_address, request_handler_class):
HTTPServer.__init__(self, server_address, request_handler_class)
self.port = None
self.friendly_name = None
self.manufacturer = None
self.manufacturer_url = None
self.model_description = None
self.model_name = None
self.model_url = None
self.serial_number = None
self.uuid = None
self.presentation_url = None


class UPNPHTTPServer(threading.Thread):
"""
A thread that runs UPNPHTTPServerBase.
"""

def __init__(self, port, friendly_name, manufacturer, manufacturer_url, model_description, model_name,
model_number, model_url, serial_number, uuid, presentation_url):
threading.Thread.__init__(self)
self.server = UPNPHTTPServerBase(('', port), UPNPHTTPServerHandler)
self.server.port = port
self.server.friendly_name = friendly_name
self.server.manufacturer = manufacturer
self.server.manufacturer_url = manufacturer_url
self.server.model_description = model_description
self.server.model_name = model_name
self.server.model_number = model_number
self.server.model_url = model_url
self.server.serial_number = serial_number
self.server.uuid = uuid
self.server.presentation_url = presentation_url

def run(self):
self.server.serve_forever()

0 comments on commit 7fc6d7d

Please sign in to comment.