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
andCONFIGID.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.
Advertisement
Multicast using ssdp:alive
messages.
3 messages for each root device:
upnp:rootdevice
uuid:device-UUID
urn:<domain>:<deviceType:ver>
2 for each embedded device:
uuid:device-UUID
urn:<domain>:<deviceType:ver>
1 for each service type in each device:
urn<domain>:<serviceType:ver>
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:alive
.
Note that 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.
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 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
-
enumerator root
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)
-
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
-
inline void setCallback(MessageDelegate delegate)
-
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.
-
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.
- 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
-
inline bool isActive()
Determine if server is running.
- Return values:
bool –
-
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
-
using MessageDelegate = Delegate<void(MessageSpec *ms)>
References
Used by
UPnP Library
Environment Variables
SoC support
esp32
esp32c2
esp32c3
esp32s2
esp32s3
esp8266
host
rp2040