1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
/*!
This module implements the UPnP discovery protocol providing availability notifications and
search capabilities.
# Specification
This section explains the UPnP discovery protocol known as _Simple Service Discovery Protocol_
(SSDP) in detail, enumerating how devices advertise and revoke their advertisements as well as
how control points search and devices respond.
Discovery is the first step in UPnP networking. When a device is added to the network, the UPnP
discovery protocol allows that device to advertise its services to control points on the network.
Similarly, when a control point is added to the network, the UPnP discovery protocol allows that
control point to search for devices of interest on the network. The fundamental exchange in both
cases is a discovery message containing a few, essential specifics about the device or one of its
services, e.g., its type, universally unique identifier, and a pointer to more detailed information.
When a new device is added to the network, it multicasts a number of discovery messages advertising
itself, its embedded devices, and its services. Any interested control point can listen to the
standard multicast address for notifications that new capabilities are available.
Similarly, when a new control point is added to the network, it multicasts a discovery message
searching for interesting devices, services, or both. All devices must listen to the standard
multicast address for these messages and must respond if any of their embedded devices or services
match the search criteria in the discovery message.
To reiterate, a control point may learn of a device of interest because that device sent discovery
messages advertising itself or because the device responded to a discovery message searching for
devices. In either case, if a control point is interested in a device and wants to learn more about
it, the control point uses the information in the discovery message to send a description query
message. The section on Description explains description messages in detail.
11
When a device is removed from the network, it should, if possible, multicast a number of discovery
messages revoking its earlier announcements, effectively declaring that its embedded devices and
services will no longer be available. When the IP address of a device is changed, it should revoke
any earlier announcements and advertise using the new IP address.
For devices and control points that have multiple network interfaces, UPnP advertisements and
searches should be sent on all network interfaces enabled for UPnP networking. Each advertisement
or search must specify an address in the `LOCATION` header that is reachable on that interface
To limit network congestion, the time-to-live (TTL) of each IP packet for each multicast message
should default to 4 and should be configurable. When the TTL is greater than 1, it is possible for
multicast messages to traverse multiple routers; therefore control points and devices using
non-AutoIP addresses must send an IGMP Join message so that routers will forward multicast messages
to them (this is not necessary when using an Auto-IP address, since packets with Auto-IP addresses
will not be forwarded by routers).
Discovery plays an important role in the interoperability of devices and control points using
different versions of UPnP networking. The UPnP Device Architecture (defined herein) is versioned
with both a major and a minor version, usually written as major.minor, where both major and minor
are integers (for example, version 2.10 is newer than version 2.2). Advances in minor versions must
be a compatible superset of earlier minor versions of the same major version. Advances in major
version are not required to be supersets of earlier versions and are not guaranteed to be backward
compatible. Version information is communicated in discovery and description messages. In the
former, each discovery message includes the version of UPnP networking that the device supports
(in the `SERVER` header); the version of device and service types supported is also included in
relevant discovery messages. As a backup, the latter also includes the same information. This
section explains the format of version information in discovery messages and specific requirements
on discovery messages to maintain compatibility with advances in minor versions.
*/
use crate::{SpecVersion, UPNP_STRING};
use os_version::{detect, OsVersion};
use std::fmt::{Display, Error, Formatter};
// ------------------------------------------------------------------------------------------------
// Public Types
// ------------------------------------------------------------------------------------------------
///
/// This represents a specific control point, this is optional for v1.0 and v1.1 messages
/// but the `friendly_name` field is required by the 2.0 specification.
///
#[derive(Clone, Debug)]
pub struct ControlPoint {
/// Specifies the friendly name of the control point. The friendly name is vendor specific.
pub friendly_name: String,
/// UUID of the control point. When the control point is implemented in a UPnP device it
/// is recommended to use the UDN of the co-located UPnP device.
pub uuid: Option<String>,
/// A control point can request that a device replies to a TCP port on the control point.
/// When this header is present it identifies the TCP port on which the device can reply to
/// the search.
pub port: Option<u16>,
}
///
/// A product name and version, used in constructing `SERVER` and `CACHE-CONTROL` headers. These
/// arespecified by UPnP vendor. String.
///
/// Field value MUST begin with the following "product tokens" (defined by HTTP/1.1).
///
#[derive(Clone, Debug)]
pub struct ProductVersion {
name: String,
version: String,
}
///
/// The set of three products, and associated version identifiers, present in both `SERVER` and
/// `CACHE-CONTROL` headers.
///
#[derive(Clone, Debug)]
pub struct ProductVersions {
product: ProductVersion,
upnp: ProductVersion,
platform: ProductVersion,
}
// ------------------------------------------------------------------------------------------------
// Implementations
// ------------------------------------------------------------------------------------------------
const DEFAULT_PRODUCT_NAME: &str = env!("CARGO_PKG_NAME");
const DEFAULT_PRODUCT_VERSION: &str = env!("CARGO_PKG_VERSION");
lazy_static! {
static ref PLATFORM_NAME: String = platform_name();
static ref PLATFORM_VERSION: String = platform_version();
}
impl Display for ProductVersion {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
write!(f, "{}/{}", self.name, self.version)
}
}
impl ProductVersion {
pub fn for_default_product() -> Self {
Self {
name: DEFAULT_PRODUCT_NAME.to_string(),
version: DEFAULT_PRODUCT_VERSION.to_string(),
}
}
pub fn for_product(name: &str, version: &str) -> Self {
Self {
name: name.to_string(),
version: version.to_string(),
}
}
pub fn for_default_upnp() -> Self {
Self {
name: UPNP_STRING.to_string(),
version: SpecVersion::default().to_string(),
}
}
pub fn for_upnp_version(version: SpecVersion) -> Self {
Self {
name: UPNP_STRING.to_string(),
version: version.to_string(),
}
}
pub fn for_platform() -> Self {
Self {
name: PLATFORM_NAME.clone(),
version: PLATFORM_VERSION.clone(),
}
}
pub fn name(&self) -> &String {
&self.name
}
pub fn version(&self) -> &String {
&self.version
}
}
// ------------------------------------------------------------------------------------------------
impl Default for ProductVersions {
fn default() -> Self {
Self {
product: ProductVersion::for_default_product(),
upnp: ProductVersion::for_default_upnp(),
platform: ProductVersion::for_platform(),
}
}
}
impl Display for ProductVersions {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
write!(f, "{} {} {}", self.product, self.upnp, self.platform)
}
}
impl ProductVersions {
pub fn new(product: ProductVersion, upnp: ProductVersion, platform: ProductVersion) -> Self {
Self {
product,
upnp,
platform,
}
}
pub fn product_version(&self) -> &ProductVersion {
&self.product
}
pub fn upnp_version(&self) -> &ProductVersion {
&self.upnp
}
pub fn platform_version(&self) -> &ProductVersion {
&self.platform
}
}
// ------------------------------------------------------------------------------------------------
// Private Functions
// ------------------------------------------------------------------------------------------------
fn platform_name() -> String {
let version = detect().expect("Could not detect platform name/version");
match version {
OsVersion::Linux(v) => format!("linux/{}", v.distro),
OsVersion::MacOS(_) => "macos".to_string(),
OsVersion::Windows(_) => "windows".to_string(),
OsVersion::OpenBSD(_) => "OpenBSD".to_string(),
_ => panic!("Unknown or unsupported platform"),
}
}
fn platform_version() -> String {
let version = detect().expect("Could not detect platform name/version");
match version {
OsVersion::Linux(v) => v.version.expect("No version information for Linux"),
OsVersion::MacOS(v) => v.version,
OsVersion::Windows(v) => v.version,
OsVersion::OpenBSD(v) => v.version,
_ => panic!("Unknown or unsupported platform"),
}
}
// ------------------------------------------------------------------------------------------------
// Modules
// ------------------------------------------------------------------------------------------------
pub mod search;
pub mod notify;