SSDP

https://en.wikipedia.org/wiki/Simple_Service_Discovery_Protocol

Provides an SSDP server implementation aiming to be fully standards compliant.

Configuration Variables

SSDP_DEBUG_VERBOSE
  • 0 (default)

  • 1: Output details of all requests. Warning: This can produce a lot of output!

UPNP_VERSION

UPnP standard to follow.

  • 1.0: (default)

  • 1.1: Incomplete

  • 2.0: Incomplete

Versions 1.1 and 2.0 require BOOTID.UPNP.ORG and CONFIGID.UPNP.ORG fields 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.

Search request

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 definitions. e.g. Basic:1 not Basic:1.0.

Search response

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.

Descriptions

The 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.

Points arising

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?

API Documentation

namespace SSDP

Typedefs

using MessageDelegate = Delegate<void(MessageSpec *ms)>

A callback function must be provided to do the actual sending.

Param 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.

Note

The message spec. is provided by the UPnP Device Host, which then gets called back to construct the message content. It then calls sendMessage().

Param msg:

Message with standard fields completed

Param ms:

Parameters for constructing message

Enums

enum class MessageType

Values:

enumerator XX
enum class NotifySubtype

SSDP Notification subtype.

Values:

enumerator XX
enumerator OTHER
enum class SearchTarget

SSDP Search target types.

Values:

enumerator root

Root devices only: upnp:rootdevice

enumerator type

or urn:{domain}:service:{serviceType}:{v}

Search for device/service type: urn:{domain}:device:{deviceType}:{v}

enumerator uuid

Search for specific device: uuid:{device-UUID}

enumerator all

All devices and services: ssdp::all

enum class SearchMatch

Determines the kind of match obtained when scanning incoming packets.

Values:

enumerator root

Matched root device.

enumerator uuid

Matched with device UUID.

enumerator type

Matched device or service type.

Functions

static const IpAddress multicastIp (239, 255, 255, 250)
DECLARE_FSTR(SSDP_DISCOVER)
DECLARE_FSTR(UPNP_ROOTDEVICE)
DECLARE_FSTR(SSDP_ALL)
NotifySubtype getNotifySubtype(const char *subtype)
DECLARE_FSTR(defaultProductNameAndVersion)
String getServerId(const String &productNameAndVersion)

Variables

static constexpr uint16_t multicastPort = 1900
Server server
template<class HeaderClass>
class BaseMessage : public HeaderClass
#include <Message.h>

class template for messages

class BasicMessage : public SSDP::BaseMessage<BasicHttpHeaders>
#include <Message.h>

Handles incoming messages.

Note

Contains name/value pairs as pointers.

class Message : public SSDP::BaseMessage<HttpHeaders>
#include <Message.h>

Message using regular HTTP header management class.

Note

More flexible than BasicMessage but requires additional memory allocations

class MessageQueue
#include <MessageQueue.h>

Queue of objects managed by a single timer.

Public Functions

inline void setCallback(MessageDelegate delegate)

Set a callback to handle sending a message @Param 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 new allocator and are deleted after sending.

Parameters:
  • 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.

See MessageSpec operator== definition for how comparison is performed.

Parameters:

ms

Return values:

bool – true if the given spec. is already queued.

unsigned remove(void *object)

Remove any messages for this object.

Return values:

unsigned – Number of messages removed

class MessageSpec
#include <MessageSpec.h>

Defines the information used to create an outgoing message.

The message queue stores these objects as a linked list.

Public Functions

inline MessageSpec(const MessageSpec &ms, SearchMatch match, void *object)

Construct a new message spec for a specific match type.

Parameters:
  • 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.

template<class Object>
inline Object *object() const

Get the target object pointer.

This is templated to provide cleaner code. Example:

MyObject* object = ms.object<MyObject>();

inline MessageType type() const

Get the message type.

inline NotifySubtype notifySubtype() const

Get the notification sub-type.

inline SearchMatch match() const

Get the search match type.

inline SearchTarget target() const

Get the search target.

inline void setTarget(SearchTarget target)

Set the search target.

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.

class Server : private UdpConnection
#include <Server.h>

Listens for incoming messages and manages queue of outgoing messages.

Todo:

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.

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 Timer and drive it from that.

Note

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.

Public Functions

bool begin(ReceiveDelegate receiveCallback, SendDelegate sendCallback)

Called from UPnP library to start SSDP server.

Note

May only be called once

Return values:

bool – true on success

void end()

Stop SSDP server.

inline bool isActive()

Determine if server is running.

Return values:

bool

bool sendMessage(const Message &msg)

Send a message immediately.

bool buildMessage(Message &msg, MessageSpec &ms)

Construct a message from the given template spec.

Parameters:
  • msg – Fields of this message will be filled out

  • ms – Spec to use for constructing message

Return values:

bool – Returns false if validation failed: message should not be sent

inline void setProduct(const String &name, const String &version)

Set product name and version contained in SSDP message USER-AGENT field.

References

Used by

Environment Variables

SoC support

  • esp32

  • esp32c2

  • esp32c3

  • esp32s2

  • esp32s3

  • esp8266

  • host

  • rp2040