UPnPsdk 0.1
Universal Plug and Play +, Software Development Kit
 
Loading...
Searching...
No Matches
Overview

The UPnP+™ Software Development Kit
General information to the SDK, its structure and requirements.


Parts 'Control Point' and 'UPnP Device'

The SDK has two parts, handling Control Points or UPnP Devices. They are largely independent of each other. You can compile both parts separately so you have resulting libraries that only handle Control Points or UPnP Devices. That make sense for installations that are only a Control Point or only a UPnP Device and is of particular interest for embedded devices. Of course you can also have both parts for hybrid applications.

Internal Libraries

We have three different internal libraries:

  • pupnp: original code from the fork, based on version 1.14.22 with all its bugs and quirks, only modified to compile as C++ code.
  • compa: compatible library, partly with re-engineered code from pupnp and with objects from UPnPsdk to emulate the API (Application Programming Interface) from pupnp. It compiles to a library that is drop in compatible to its ABI (Application Binary Interface) so you can just replace an old binary libupnp with the binary compa library without modifications or recompiling programs. It fixes bugs and add additional issues that are given by the new UPnPsdk objects. If old programs depend on bugs of the old library they may behave different.
  • UPnPsdk: completely new written object oriented UPnP Software Developement Kit. It is intended to support only this library after a transition period.

To manage a smooth transition from old pUPnP "C" code to the object oriented C++ class library there are different compile modes specified.

code with library build mode
old pupnp yes verify (if old programs still run as expected)
old compa yes compatibility (used to start transition)
new pupnp no not supported
old modified compa yes portability to UPnPsdk (step by step ongoing transition)
new UPnPsdk yes new project

Main Components

The Software Development Kit contains some main components that are associated with a specific step of the UPnP™ Device Architecture 2.0.
These components are:

order component part of
1 miniserver Step 0: Addressing
2 webserver Step 1: Discovery
3 SSDP Step 1: Discovery
4 SOAP Step 3: Control
5 GENA Step 4: Eventing

The order shows the dependency. Each component depends on its predecessor. For example if selecting SSDP on configuring to compile, this selects also automatically the webserver (only that) and the webserver selects miniserver. If you select GENA for compiling you have a full featured configuration with all main components. There is no need to select previous moduls. Also the abstraction will increase with the order. For example no component except miniserver will handle network sockets.

Note
This order is an important implicit dependency. It is always expected that it is respected. No internal checks are made about this. The compiler will remind you if you forget it.

Promise to throw "no exception"

There are several functions and methods that are specified to noexcept. This may not always be true with debugging or TRACE enabled because there we have extended use of std::string objects for output to std::cerr. Both may throw exceptions in rare cases like exceeding max. length of a std::string or error on memory allocation or modified output flags of the std::cerr output stream by the program using the SDK. The result of such an unexpected exception is the immediate termination of the program as specified for noexcept. But I assume that debugging or TRACE is only a temporary setting not used for production code and is under special observation by the developer so that he can evaluate these exceptions.

Socket option "reuse address"

There is an issue when a network connection unexpectedly dies. The port then has normaly to TIME_WAIT util it can use its old netaddress again. This may not be acceptable for certain server applications. So there is a socket option SO_REUSEADDR to immediately reuse a given netaddress, but with the risk of network errors and security issues. For more details have a look at Bind: Address Already in Use.

I don't set the option to immediately reuse an address of a local connected socket after the connection closed unexpectedly. Instead I respect the TIME_WAIT. For an immediate reconnection I use a new address. A new netaddress is already given with a changed port so I just use the same ip address with another port. I always reset socket option SO_REUSEADDR with constructing a socket object on all platforms.

A TIME_WAIT is not necessary if the remote control point closes the connection. UPnPsdk uses the protocol to signal the remote control point to shutdown the connection if it support it.

TIME_WAIT is state of TCP connection, not the port. Every TCP connection identifies by tuple (local-address, local-port, remote-address, remote-port). So if the client connect to server using new (dynamic) local port then new TCP connection is created and TIME_WAIT isn't issue.

This is unclear on Microsoft Windows. THERE WE HAVE AN IMPORTANT SECURITY ISSUE!
In the past Windows had a very bad network security on low level socket handling as documented at Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE. Microsoft has fixed this since Windows Server 2003. The network stack now behaves like it does by default on other platforms. Strangely, they call it advanced security, which is standard on other major operating systems. But this isn't done by default. The developer has to take this into account by using the socket option SO_EXCLUSIVEADDRUSE. I always set this option with constructing a socket object on a WIN32 platform.

LFS: Large-File Support

For embedded devices 32 bit architectures are still supported. Only for this architectures Large-file support is needed to handle files greater 2 GiB. We need this because we have to handle video files that may exceed this limit. For targets with 64 bit architecture LFS is not relevant.

The problem is the access to files. The natural limit is given by the address structure. With 32 bit you can access max. 2 GiB, with 64 bit architectures you can access usually 256 terabytes for example with fseek. To handle large files with 32 bit you have to provide new functions, e.g. fseek64. Microsoft Windows is going this way and provides fseek and _fseeki64. POSIX is going an other way. It provides fseeko() and ftello() functions that are identical to fseek() and ftell(), respectively, except that the offset argument of fseeko() and the return value of ftello() is of type off_t instead of long. On some architectures, both off_t and long are 32-bit types, but defining _FILE_OFFSET_BITS=64 as compile option will turn off_t into a 64-bit type. This way you can compile your code to use LFS or not without modifications.

Note
UPnPsdk always supports large files and the application using the SDK MUST also do it to avoid a program crash due to different pointer sizes. If you try to compile for a 32 bit application without LFS you will get a compile error telling you to enable LFS for your application.

References:

Visibility support

Visibility Support provides a powerful optimization. I use it as described at the GCC Wiki - Visibility. It only belongs to shared libraries. Here in short the needed steps configured for this software development kit:

  • Enable Visibility Support with CMake on the whole project:
    set(CMAKE_CXX_VISIBILITY_PRESET hidden)
    set(CMAKE_VISIBILITY_INLINES_HIDDEN 1)
    # or only on a target:
    set_target_properties(UPnPsdk_shared PROPERTIES
    CXX_VISIBILITY_PRESET hidden
    VISIBILITY_INLINES_HIDDEN ON)

  • When building a shared library set its compile definitions to UPnPsdk_SHARE and UPnPsdk_EXPORTS. You should declare UPnPsdk_SHARE to be PUBLIC. With nested use of libraries this is only needed for the first library. CMake propagates the compile definition. With other combinations you have to respect CMakes propagation rules for PRIVATE, PUBLIC, INTERFACE and it may be necessary to also declare UPnPsdk_SHARE on following libraries and executables. In result every shared library and executable using it, must see UPnPsdk_SHARE.
    Never set UPnPsdk_EXPORTS to PUBLIC. This may see an executable target that should not export symbols.
    add_library(UPnPsdk_shared SHARED
    ${UPnPsdk_SOURCE_FILES})
    target_compile_definitions(UPnPsdk_shared
    PUBLIC UPnPsdk_SHARE
    PRIVATE UPnPsdk_EXPORTS)
    add_library(compa_shared SHARED
    ${COMPA_SOURCE_FILES})
    target_compile_definitions(compa_shared
    PRIVATE UPnPsdk_EXPORTS)
    target_link_libraries(compa_shared
    PUBLIC UPnPsdk_shared)
    add_executable(api_calls-csh
    ${API_CALLS_SOURCE_FILES})
    target_link_libraries(api_calls-csh
    PRIVATE compa_shared)
    But you need with:
    target_link_libraries(compa_shared
    PRIVATE UPnPsdk_shared)
    add_executable(api_calls-csh
    ${API_CALLS_SOURCE_FILES})
    target_compile_definitions(api_calls-csh
    PRIVATE UPnPsdk_SHARE)
    target_link_libraries(api_calls-csh
    PRIVATE compa_shared)

  • In your header files, wherever you want an interface or API made public outside the current Dynamic Shared Object, place UPnPsdk_VIS or UPnPsdk_API in struct, class and function declarations you wish to make public visible. You should not specify it in the definition of your source files. On Microsoft Windows Visual Studio it does not compile due to an error. You should never do it on templated or static functions because they are defined to be local.
    UPnPsdk_VIS int PublicFreeFunc();
    class UPnPsdk_VIS PublicClass{};
    struct UPnPsdk_VIS PublicStruct{};
    #define UPnPsdk_VIS
    Prefix to export symbol for external use.

  • For optimization with using UPnPsdk_LOCAL look at the GCC Wiki - Visibility. If you have a publicy visibile class UPnPsdk_VIS PublicClass() then all its member functions are also publicy visible. To keep them private you can prefix them with UPnPsdk_LOCAL.