arcam / CocoaUPnP

CocoaUPnP is a logical progression of upnpx; designed to be easy, modern and block-based.
MIT License
85 stars 40 forks source link

The value for "USER-AGENT" in the M-SEARCH message does not comply to the UPnP standard. It also contains version issues. #60

Open funnel20 opened 5 years ago

funnel20 commented 5 years ago

When logging the SSDP messages from our iOS app with CocoaUPnP, we see that the M-SEARCH message has multiple issues for the "USER-AGENT" value.

Wireshark Log:

M-SEARCH * HTTP/1.1 HOST: 239.255.255.250:1900 MAN: "ssdp:discover" ST: urn:schemas-upnp-org:device:MediaServer:1 MX: 3 USER-AGENT: My App/4 (iPhone; iOS 12.4) CocoaSSDP/0.1.0/1

Issues

  1. According to section 1.3.2 of the UPnP Device Architecture 1.1 the value should have the following syntax: USER-AGENT: OS/version UPnP/1.1 product/version

    The first product token identifes the operating system in the form OS name/OS version, the second token represents the UPnP version and MUST be UPnP/1.1, and the third token identifes the product using the form product name/product version. For example, “USER-AGENT: unix/5.1 UPnP/1.1 MyProduct/1.0”

This fails twice:

  1. The first product token should only be OS name/OS version and no combination of app and OS between braces.

  2. The first product token shows the Build value from Xcode as version (4 in this example), instead of the Version string. Apparently the wrong key is used in _userAgentString, which should be "CFBundleShortVersionString": https://github.com/arcam/CocoaUPnP/blob/3a4b58a82fa0e3db142f5d1be55a98c6c464aac9/CocoaUPnP/Networking/SSDP/SSDPServiceBrowser.m#L307-L311

  3. The current version of CocoaSSDP is 2.0.2, not 0.1.0 as used in the user-agent. Root cause: A hardcoded const SSDPVersionString is used in SSDPServiceBrowser: https://github.com/arcam/CocoaUPnP/blob/3a4b58a82fa0e3db142f5d1be55a98c6c464aac9/CocoaUPnP/Networking/SSDP/SSDPServiceBrowser.m#L38

Solution To solve all these issues, I would recommend to follow the UPnP docu: USER-AGENT: OS/version UPnP/1.1 product/version

For this example that would be: USER-AGENT: iOS/12.4 UPnP/1.1 My App/1.2.3

That would also reduce maintaining SSDPVersionString, which obviously will be forgotten for each update.

So in code: remove /1 from USER_AGENT:

- (NSString *)_prepareSearchRequestWithServiceType:(NSString *)serviceType {
    NSString *userAgent = [self _userAgentString];

    return [NSString stringWithFormat:
            @"M-SEARCH * HTTP/1.1\r\n"
            "HOST: %@:%d\r\n"
            "MAN: \"ssdp:discover\"\r\n"
            "ST: %@\r\n"
            "MX: 3\r\n"
            "USER-AGENT: %@\r\n\r\n\r\n",
            SSDPMulticastGroupAddress,
            SSDPMulticastUDPPort,
            serviceType ?: @"ssdp:all",
            userAgent];
}

And apply proper syntax and use of correct keys to create the user-agent value:

- (NSString *)_userAgentString {
    NSString *userAgent = nil;
    NSDictionary *bundleInfos = [[NSBundle mainBundle] infoDictionary];
    NSString *bundleExecutable = bundleInfos[(__bridge NSString *)kCFBundleExecutableKey] ?: bundleInfos[(__bridge NSString *)kCFBundleIdentifierKey];

#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
    userAgent = [NSString stringWithFormat:@"%@/%@ UPnP/1.1 %@/%@",
                 [[UIDevice currentDevice] systemName],
                 [[UIDevice currentDevice] systemVersion],
                 bundleExecutable,
                 bundleInfos[@"CFBundleShortVersionString"]];

And similar for macOS.