Last active
October 2, 2020 14:02
-
-
Save AVGP/dce357288ad42ba96796c7ad27d6a184 to your computer and use it in GitHub Desktop.
UPnP test server (incomplete)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<root xmlns="urn:schemas-upnp-org:device-1-0" xmlns:dlna="urn:schemas-dlna-org:device-1-0"> | |
<specVersion> | |
<major>1</major> | |
<minor>0</minor> | |
</specVersion> | |
<device> | |
<dlna:X_DLNADOC>DMR-1.50</dlna:X_DLNADOC> | |
<deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType> | |
<friendlyName>YoloCast</friendlyName> | |
<manufacturer/> | |
<manufacturerURL/> | |
<modelDescription>Node.js DLNA server</modelDescription> | |
<modelName/> | |
<modelNumber/> | |
<UDN>uuid:$UUID</UDN> | |
<serviceList> | |
<service> | |
<serviceType>urn:schemas-upnp-org:service:ContentDirectory:1</serviceType> | |
<serviceId>urn:upnp-org:serviceId:ContentDirectory</serviceId> | |
<SCPDURL>http://$IP:8088/cds.xml</SCPDURL> | |
<controlURL>http://$IP:8088/cds_ctrl</controlURL> | |
<eventSubURL>http://$IP:8088/cds_evt</eventSubURL> | |
</service> | |
<service> | |
<serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType> | |
<serviceId>urn:upnp-org:serviceId:ConnectionManager</serviceId> | |
<SCPDURL>http://$IP:8088/cms.xml</SCPDURL> | |
<controlURL>http://$IP:8088/cms_ctrl</controlURL> | |
<eventSubURL>http://$IP:8088/cms_evt</eventSubURL> | |
</service> | |
</serviceList> | |
<iconList> | |
<icon> | |
<mimetype>image/png</mimetype> | |
<width>48</width> | |
<height>48</height> | |
<depth>24</depth> | |
<url>/icon_large.png</url> | |
</icon> | |
<icon> | |
<mimetype>image/png</mimetype> | |
<width>24</width> | |
<height>24</height> | |
<depth>24</depth> | |
<url>/icon_small.png</url> | |
</icon> | |
</iconList> | |
</device> | |
</root> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var datagram = require('dgram') | |
var http = require('http') | |
var fs = require('fs') | |
var path = require('path') | |
var uuid = require('uuid/v4') | |
var mime = require('mime') | |
const MULTICAST_ADDR = '239.255.255.250' | |
const DEVICE_UUID = 'cafebabe-dead-beef-cafe-c0c0f00dcafe' | |
const PORT = 1900 | |
const IP = '192.168.1.18' | |
/*const announcement = 'HTTP/1.1 200 OK\r\n' | |
+ 'CACHE-CONTROL: max-age=1800\r\n' | |
+ `DATE: ${Date.now()}\r\n` | |
+ 'EXT:\r\n' | |
+ 'Location: http://192.168.1.7:5000/yolo.xml\r\n' | |
+ 'SERVER: Yolo UPnP\r\n' | |
+ 'ST: urn:schemas-upnp-org:device:MediaServer:1\r\n' | |
+ `USN: uuid:${DEVICE_UUID}::urn:schemas-upnp-org:device:MediaServer:1\r\n\r\n`*/ | |
const announcements = [ | |
// 0: general root device announcement | |
'NOTIFY * HTTP/1.1\r\n' | |
+ `HOST: 239.255.255.250:${PORT}\r\n` | |
+ 'CACHE-CONTROL: max-age=1800\r\n' | |
+ `LOCATION: http://${IP}:8088/dms.xml\r\n` | |
+ 'NT: upnp:rootdevice\r\n' | |
+ 'NTS: ssdp:alive\r\n' | |
+ 'SERVER: unix/5.1 UPnP/1.1 YoloCast/1.0\r\n' | |
+ `USN: uuid:${DEVICE_UUID}::upnp:rootdevice\r\n` | |
+ 'BOOTID.UPNP.ORG: 1\r\n' | |
+ 'CONFIGID.UPNP.ORG: 0\r\n\r\n', | |
// 1: device announcement | |
'NOTIFY * HTTP/1.1\r\n' | |
+ `HOST: 239.255.255.250:${PORT}\r\n` | |
+ 'CACHE-CONTROL: max-age=1800\r\n' | |
+ `LOCATION: http://${IP}:8088/dms.xml\r\n` | |
+ `NT: uuid:${DEVICE_UUID}\r\n` | |
+ 'NTS: ssdp:alive\r\n' | |
+ 'SERVER: unix/5.1 UPnP/1.1 YoloCast/1.0\r\n' | |
+ `USN: uuid:${DEVICE_UUID}\r\n` | |
+ 'BOOTID.UPNP.ORG: 1\r\n' | |
+ 'CONFIGID.UPNP.ORG: 0\r\n\r\n', | |
// 2: device type announcement | |
'NOTIFY * HTTP/1.1\r\n' | |
+ 'HOST: 239.255.255.250:${PORT}\r\n' | |
+ 'CACHE-CONTROL: max-age=1800\r\n' | |
+ `LOCATION: http://${IP}:8088/dms.xml\r\n` | |
+ 'NT: urn:schemas-upnp-org:device:MediaServer:1\r\n' | |
+ 'NTS: ssdp:alive\r\n' | |
+ 'SERVER: unix/5.1 UPnP/1.1 YoloCast/1.0\r\n' | |
+ `USN: uuid:${DEVICE_UUID}::urn:schemas-upnp-org:device:MediaServer:1\r\n` | |
+ 'BOOTID.UPNP.ORG: 1\r\n' | |
+ 'CONFIGID.UPNP.ORG: 0\r\n\r\n', | |
// 3: service announcement: Content Directory | |
'NOTIFY * HTTP/1.1\r\n' | |
+ `HOST: 239.255.255.250:${PORT}\r\n` | |
+ 'CACHE-CONTROL: max-age=1800\r\n' | |
+ `LOCATION: http://${IP}:8088/cds.xml\r\n` | |
+ 'NT: urn:schemas-upnp-org:service:ContentDirectory:1\r\n' | |
+ 'NTS: ssdp:alive\r\n' | |
+ 'SERVER: unix/5.1 UPnP/1.1 YoloCast/1.0\r\n' | |
+ `USN: uuid:${DEVICE_UUID}::urn:schemas-upnp-org:service:ContentDirectory:1\r\n` | |
+ 'BOOTID.UPNP.ORG: 1\r\n' | |
+ 'CONFIGID.UPNP.ORG: 0\r\n\r\n', | |
// 4: service announcement: Connection Management | |
'NOTIFY * HTTP/1.1\r\n' | |
+ `HOST: 239.255.255.250:${PORT}\r\n` | |
+ 'CACHE-CONTROL: max-age=1800\r\n' | |
+ `LOCATION: http://${IP}:8088/cms.xml\r\n` | |
+ 'NT: urn:schemas-upnp-org:service:ConnectionManager:1\r\n' | |
+ 'NTS: ssdp:alive\r\n' | |
+ 'SERVER: unix/5.1 UPnP/1.1 YoloCast/1.0\r\n' | |
+ `USN: uuid:${DEVICE_UUID}::urn:schemas-upnp-org:service:ConnectionManager:1\r\n` | |
+ 'BOOTID.UPNP.ORG: 1\r\n' | |
+ 'CONFIGID.UPNP.ORG: 0\r\n\r\n' | |
] | |
var ssdpServer = datagram.createSocket('udp4') | |
var httpServer = http.createServer(function(req, res) { | |
const fullPath = path.join(__dirname, req.url); | |
if (req.url.match(/\.xml$/)) return sendHttpControlData(fullPath, res) | |
console.log('Media request', req.url); | |
try { | |
const size = fs.statSync(fullPath).size | |
res.writeHead(200, { | |
'Content-Type': mime.getType(fullPath), | |
'Content-Length': size | |
}); | |
var stream = fs.createReadStream(fullPath); | |
stream.pipe(res); | |
} catch(e) { | |
res.writeHead(404, 'not found') | |
} | |
}); | |
ssdpServer.on('listening', async function() { | |
console.log('listening...') | |
// announce root device | |
ssdpServer.send(Buffer.from(announcements[0]), 1900, '239.255.255.250') | |
await sleep(200) | |
ssdpServer.send(Buffer.from(announcements[1]), 1900, '239.255.255.250') | |
await sleep(200) | |
ssdpServer.send(Buffer.from(announcements[2]), 1900, '239.255.255.250') | |
await sleep(200) | |
ssdpServer.send(Buffer.from(announcements[3]), 1900, '239.255.255.250') | |
await sleep(200) | |
ssdpServer.send(Buffer.from(announcements[4]), 1900, '239.255.255.250') | |
await(1000) | |
ssdpServer.addMembership(MULTICAST_ADDR) | |
}) | |
ssdpServer.on('message', (msg, rInfo) => { | |
if (msg.toString('utf-8').match('M-SEARCH')) { | |
console.log('RECV', msg.toString('utf-8'), rInfo) | |
// console.log('announced!', announcement) | |
// ssdpServer.send(Buffer.from(announcement), 8088, rInfo.address) | |
} | |
}) | |
ssdpServer.bind(PORT) | |
httpServer.listen(8088) | |
function sendHttpControlData(fullPath, res) { | |
console.log('Control request', fullPath) | |
try { | |
var content = fs.readFileSync(fullPath, 'utf-8'); | |
content = content.replace(/\$UUID/g, DEVICE_UUID) | |
content = content.replace(/\$IP/g, IP) //TODO: Figure out local IP | |
} catch(e) { | |
console.log(fullPath + ' not found') | |
res.writeHead(404, 'Not found') | |
res.write('') | |
return | |
} | |
res.writeHead(200, { | |
'Content-Type': mime.getType(fullPath), | |
'Content-Length': content.length | |
}); | |
res.write(content) | |
return | |
} | |
function sleep(ms) { | |
return new Promise(resolve => { | |
setTimeout(resolve, ms); | |
}); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment