Provides an SSDP server implementation aiming to be fully standards compliant.
1: Output details of all requests. Warning: This can produce a lot of output!
UPnP standard to follow.
Versions 1.1 and 2.0 require
CONFIGID.UPNP.ORGfields which are not currently implemented.
Key points from UPnP 2.0 specification
I’m choosing to avoid the issue of ‘multi-homed’ devices, where both IPv4 and IPv6 are being used, and probably also WiFi+Ethernet combinations. Basically whenever multiple IP stacks are involved.
3 messages for each root device:
upnp:rootdevice uuid:device-UUID urn:<domain>:<deviceType:ver>
2 for each embedded device:
1 for each service type in each device:
Initial advertisements ‘should be sent as quickly as possible’. Subsequent advertisements ‘are allowed to be spread over time’.
For orderly shutdown, multicast
ssdp:byebye messages as for
ssdp:update messages are only mentioned in the spec. wrt. mult-homed devices.
Devices should wait a random interval (e.g. 0 - 100 ms) before sending an initial set of advertisements in order to reduce the likelihood of network storms; this random interval should also be applied on occasions where the device obtains a new IP address or a new UPnP-enabled interface is installed. Due to the unreliable nature of UDP, devices should send the entire set of discovery messages more than once with some delay between sets e.g. a few hundred milliseconds. To avoid network congestion discovery messages should not be sent more than three times. The device shall re-send its advertisements periodically prior to expiration of the duration specified in the CACHE-CONTROL header field; it is Recommended that such refreshing of advertisements be done at a randomly-distributed interval of less than one-half of the advertisement expiration time, so as to provide the opportunity for recovery from lost advertisements before the advertisement expires, and to distribute over time the advertisement refreshment of multiple devices on the network in order to avoid spikes in network traffic. Note that UDP packets are also bounded in length (perhaps as small as 512 Bytes in some implementations); each discovery message shall fit entirely in a single UDP packet. There is no guarantee that the above 3+2d+k messages will arrive in a particular order.
M-SEARCH: “Please tell me about yourselves, but don’t all shout at once.”
ssdp:all Search for all devices and services. upnp:rootdevice Search for root devices only. uuid:device-UUID Search for a particular device. urn:<domain>:device:deviceType:ver Search for any device of this type. urn:<domain>:service:serviceType:ver Search for any service of this type. Period characters in <domain> are always substituted with hyphens (RFC 2141).
Not clear on how to handle version numbers at present. The specs. say only minor versions
are backward compatible, which why perhaps we only see major numbers in interface
Any device responding to a unicast M-SEARCH should respond within 1 second.
In response to an M-SEARCH request, if ST header in request was:
ssdp:all Respond 3+2d+k times for a root device with d embedded devices and s embedded services but only k distinct service types. Value for ST header must be the same as for the NT header in NOTIFY messages with ssdp:alive. upnp:rootdevice Respond once for root device. uuid:device-UUID Respond once for each matching device, root or embedded. urn:<domain>:device:deviceType:v Respond once for each matching device, root or embedded. Should specify the version of the device type contained in the M-SEARCH request. urn:<domain>:service:serviceType:v Respond once for each matching service type. Should specify the version of the service type contained in the M-SEARCH request.
LOCATION field is for the device description or enclosing device in the case of a service.
This implies that we never respond with a service description, which makes sense:
The device description provides key information about its services
The service description contains action lists or state variable tables
Only the device description is required to learn about services, whilst the service description is only required if the Control Point needs to interact with that service.
So we need a filter which then gets passed through the device stack. Each response must be sent on a schedule, not all together, so we’ll need to set up a timer. We’ll also need to track state something like the DescriptionStream. Actually, what we can do is create an enumerator which iterates through the entire device stack. That will take out the complexity from here and DescriptionStream. We’ll need an additional Item tag so we can differentiate. This can either be a virtual method or we could use a union with all the different Item types plus a separate tag field. That could also contain the search filter information as input.
Move all this stuff into an SsdpResponder class?
using MessageDelegate = Delegate<void(MessageSpec *ms)>
A callback function must be provided to do the actual sending.
ms – Message spec. to action, must delete when finished with it
using ReceiveDelegate = Delegate<void(BasicMessage &message)>
Callback type for handling an incoming message.
using SendDelegate = Delegate<void(Message &msg, MessageSpec &ms)>
Callback type for sending outgoing message.
The message spec. is provided by the UPnP Device Host, which then gets called back to construct the message content. It then calls
msg – Message with standard fields completed
ms – Parameters for constructing message
SSDP Search target types.
Root devices only:
Search for device/service type:
Search for specific device:
- enumerator root
- static const IpAddress multicastIp (239, 255, 255, 250)
class BaseMessage : public HeaderClass
- #include <Message.h>
class template for messages
class BasicMessage : public SSDP::BaseMessage<BasicHttpHeaders>
- #include <Message.h>
Handles incoming messages.
Contains name/value pairs as pointers.
class Message : public SSDP::BaseMessage<HttpHeaders>
- #include <Message.h>
Message using regular HTTP header management class.
More flexible than BasicMessage but requires additional memory allocations
- #include <MessageQueue.h>
Queue of objects managed by a single timer.
inline void setCallback(MessageDelegate delegate)
Set a callback to handle sending a message delegate.
void add(MessageSpec *ms, uint32_t intervalMs)
Schedule a message to start after the given interval has elapsed.
The UPnP spec. requires that messages are sent after random delays, hence the interval. MessagesSpec objects must be created using the
newallocator and are deleted after sending.
ms – The template spec. for constructing the message(s)
intervalMs – How long to wait before sending
bool contains(const MessageSpec &ms) const
Determine if a matching message specification is already queued.
MessageSpecoperator== definition for how comparison is performed.
bool – true if the given spec. is already queued.
unsigned remove(void *object)
Remove any messages for this object.
unsigned – Number of messages removed
- inline void setCallback(MessageDelegate delegate)
- #include <MessageSpec.h>
Defines the information used to create an outgoing message.
The message queue stores these objects as a linked list.
inline MessageSpec(const MessageSpec &ms, SearchMatch match, void *object)
Construct a new message spec for a specific match type.
ms – Template message spec
match – The match type
object – Target for message
inline IpAddress remoteIp() const
Get the remote IP address.
inline uint16_t remotePort() const
Get the remote port number.
inline Object *object() const
Get the target object pointer.
This is templated to provide cleaner code. Example:
MyObject* object = ms.object<MyObject>();
inline void setRemote(IpAddress address, uint16_t port)
Set the remote address and port.
inline void setRepeat(uint8_t count)
Set number of times to repeat message.
inline uint8_t repeat() const
Get current repeat value.
inline bool shouldRepeat()
Check if message should be repeated and adjust counter.
- inline MessageSpec(const MessageSpec &ms, SearchMatch match, void *object)
class Server : private UdpConnection
- #include <Server.h>
Listens for incoming messages and manages queue of outgoing messages.
Randomise the time as required by MX and keep queue ordered by time. Each message is 12 bytes, adding time would make this 16. Need to handle alives < 1/2 expiry time as well so timer will always be active. Could also use a linked list so an additional pointer would make it 20 bytes.
The spec. talks about random intervals, etc. but to keep things simple we just use a timer to spread all these messages out at regular intervals.
Note: This is basically another timer queue, so we could use software timers directly but potentially there could be a lot of them. Better I think to use a single
Timerand drive it from that.
bool begin(ReceiveDelegate receiveCallback, SendDelegate sendCallback)
May only be called once
bool – true on success
inline bool isActive()
Determine if server is running.
bool buildMessage(Message &msg, MessageSpec &ms)
Construct a message from the given template spec.
msg – Fields of this message will be filled out
ms – Spec to use for constructing message
bool – Returns false if validation failed: message should not be sent
- using MessageDelegate = Delegate<void(MessageSpec *ms)>