--- a/libupnpp/device/device.cxx
+++ b/libupnpp/device/device.cxx
@@ -29,8 +29,10 @@
#include <algorithm>
#include <mutex>
#include <condition_variable>
+#include <thread>
#include "libupnpp/log.hxx"
+#include "libupnpp/smallut.h"
#include "libupnpp/ixmlwrap.hxx"
#include "libupnpp/upnpplib.hxx"
#include "libupnpp/upnpputils.hxx"
@@ -40,10 +42,40 @@
using namespace std;
using namespace UPnPP;
+#if UPNP_VERSION_MINOR < 8 && !defined(UpnpEvent_get_SID_cstr)
+typedef struct Upnp_Event UpnpEvent;
+#define UpnpEvent_get_SID_cstr(x) ((x)->Sid)
+#define UpnpEvent_get_EventKey(x) ((x)->EventKey)
+#define UpnpEvent_get_ChangedVariables(x) ((x)->ChangedVariables)
+typedef struct Upnp_Action_Request UpnpActionRequest;
+#define UpnpActionRequest_get_ErrCode(x) ((x)->ErrCode)
+#define UpnpActionRequest_get_ActionName_cstr(x) ((x)->ActionName)
+#define UpnpActionRequest_get_DevUDN_cstr(x) ((x)->DevUDN)
+#define UpnpActionRequest_get_ActionRequest(x) ((x)->ActionRequest)
+#define UpnpActionRequest_get_ActionResult(x) ((x)->ActionResult)
+typedef struct Upnp_State_Var_Request UpnpStateVarRequest;
+#define UpnpStateVarRequest_get_DevUDN_cstr(x) ((x)->DevUDN)
+#define UpnpStateVarRequest_get_ServiceID_cstr(x) ((x)->ServiceID)
+#define UpnpStateVarRequest_get_StateVarName_cstr(x) ((x)->StateVarName)
+typedef struct Upnp_Subscription_Request UpnpSubscriptionRequest;
+#define UpnpSubscriptionRequest_get_ServiceId_cstr(x) ((x)->ServiceId)
+#define UpnpSubscriptionRequest_get_UDN_cstr(x) ((x)->UDN)
+#define UpnpSubscriptionRequest_get_SID_cstr(x) ((x)->Sid)
+#endif
+
+#if UPNP_VERSION_MAJOR > 1 || (UPNP_VERSION_MAJOR==1 && UPNP_VERSION_MINOR >= 8)
+#define CBCONST const
+#else
+#define CBCONST
+#endif
+
namespace UPnPProvider {
class UpnpDevice::Internal {
public:
+ Internal(UpnpDevice *dev)
+ : me(dev) {}
+
/* Generate event: called by the device event loop, which polls
* the services. */
void notifyEvent(const std::string& serviceId,
@@ -55,21 +87,44 @@
findService(const std::string& serviceid);
/* Per-device callback */
- int callBack(Upnp_EventType et, void* evp);
-
- UPnPP::LibUPnP *lib;
- std::string deviceId;
- std::string description;
-
+ int callBack(Upnp_EventType et, const void* evp);
+
+ UpnpDevice *me{nullptr};
+ UpnpDevice *rootdev{nullptr};
+ UPnPP::LibUPnP *lib{nullptr};
+ string deviceId;
+
+ // In case startloop has been called: the event loop thread.
+ std::thread loopthread;
+
+ // Subdirectory of the virtual file tree where we store the XML files
+ string devsubd;
+
+ struct DevXML {
+ // Device properties XML fragment: friendlyName, deviceType
+ // must be in there UDN must not because we add it. Others can
+ // be added: modelName, serialNumber, manufacturer etc.
+ string propsxml;
+ // Description xml service list. Incrementally built as the
+ // services are added
+ string servicexml;
+ };
+ // This object description XML
+ DevXML myxml;
+
+ // In case there are embedded devices, here is where they store their XML
+ // fragments. The key is the embedded device UDN/aka deviceId
+ map<string, DevXML> embedxml;
+
// We keep the services in a map for easy access from id and in a
// vector for ordered walking while fetching status. Order is
// determine by addService() call sequence.
std::unordered_map<std::string, UpnpService*> servicemap;
std::vector<std::string> serviceids;
std::unordered_map<std::string, soapfun> calls;
- bool needExit;
- /* My device handle */
- UpnpDevice_Handle dvh;
+ bool needExit{false};
+ /* My libupnp device handle */
+ UpnpDevice_Handle dvh{0};
/* Lock for device operations. Held during a service callback
Must not be held when using dvh to call into libupnp */
std::mutex devlock;
@@ -80,9 +135,9 @@
class UpnpDevice::InternalStatic {
public:
/* Static callback for libupnp. This looks up the appropriate
- * device using the device ID (UDN), the calls its callback
+ * device using the device ID (UDN), then calls its callback
* method */
- static int sCallBack(Upnp_EventType et, void* evp, void*);
+ static int sCallBack(Upnp_EventType et, CBCONST void* evp, void*);
/** Static array of devices for dispatching */
static std::unordered_map<std::string, UpnpDevice *> devices;
@@ -120,20 +175,23 @@
static const int expiretime = 3600;
-UpnpDevice::UpnpDevice(const string& deviceId,
- const std::unordered_map<string, VDirContent>& files)
+// Stuff we transform in the uuids when we use them in urls
+static string replchars{"\"#%;<>?[\\]^`{|}:/ "};
+
+UpnpDevice::UpnpDevice(const string& deviceId)
{
if (o == 0 && (o = new InternalStatic()) == 0) {
LOGERR("UpnpDevice::UpnpDevice: out of memory" << endl);
return;
}
- if ((m = new Internal()) == 0) {
+ if ((m = new Internal(this)) == 0) {
LOGERR("UpnpDevice::UpnpDevice: out of memory" << endl);
return;
}
m->deviceId = deviceId;
- m->needExit = false;
- //LOGDEB("UpnpDevice::UpnpDevice(" << m->deviceId << ")" << endl);
+ m->devsubd = string("/") + neutchars(deviceId, replchars, '-') +
+ string("/");
+ //LOGDEB("UpnpDevice::UpnpDevice(" << deviceId << ")" << endl);
m->lib = LibUPnP::getLibUPnP(true);
if (!m->lib) {
@@ -160,47 +218,24 @@
}
o->devices[m->deviceId] = this;
}
-
- if (!files.empty()) {
- // files will be empty for embedded devices, and hence
- // description too (we test description emptyness to
- // discriminate root/embedded in other places).
- VirtualDir* theVD = VirtualDir::getVirtualDir();
- if (theVD == 0) {
- LOGFAT("UpnpDevice::UpnpDevice: can't get VirtualDir" << endl);
- return;
- }
-
- for (const auto& it : files) {
- if (!path_getsimple(it.first).compare("description.xml")) {
- m->description = it.second.content;
- break;
- }
- }
- if (m->description.empty()) {
- LOGFAT("UpnpDevice::UpnpDevice: no description.xml in xmlfiles"
- << endl);
- return;
- }
-
- for (const auto& it : files) {
- string dir = path_getfather(it.first);
- string fn = path_getsimple(it.first);
- // description.xml will be served by libupnp from / after
- // inserting the URLBase element (which it knows how to
- // compute), so we make sure not to serve our version from
- // the virtual dir (if it is in /, it would override
- // libupnp's).
- if (fn.compare("description.xml")) {
- theVD->addFile(dir, fn, it.second.content, it.second.mimetype);
- }
- }
+}
+
+UpnpDevice::UpnpDevice(UpnpDevice* rootdev, const string& deviceId)
+ : UpnpDevice(deviceId)
+{
+ if (nullptr != rootdev) {
+ m->rootdev = rootdev;
+ rootdev->m->embedxml[deviceId] = UpnpDevice::Internal::DevXML();
}
}
UpnpDevice::~UpnpDevice()
{
- if (!m->description.empty()) {
+ shouldExit();
+ if (m->loopthread.joinable())
+ m->loopthread.join();
+
+ if (nullptr != m->rootdev) {
UpnpUnRegisterRootDevice(m->dvh);
}
@@ -210,7 +245,12 @@
o->devices.erase(it);
}
-bool UpnpDevice::ipv4(string *host, unsigned short *port) const
+const string& UpnpDevice::getDeviceId() const
+{
+ return m->deviceId;
+}
+
+bool UpnpDevice::ipv4(string *host, unsigned short *port)
{
char *hst = UpnpGetServerIpAddress();
if (hst == 0) {
@@ -225,31 +265,100 @@
return true;
}
+static string ipv4tostrurl(const string& host, unsigned short port)
+{
+ return string("http://") + host + ":" + SoapHelp::i2s(port);
+}
+
+// Calls registerRootDevice() to register us with the lib and start up
+// the web server for sending out description files.
bool UpnpDevice::Internal::start()
{
- // Start up the web server for sending out description files. This also
- // calls registerRootDevice()
+ // Nothing to do if I am an embedded device: advertisement is
+ // handled by my root device
+ if (nullptr != rootdev) {
+ return true;
+ }
+
+ // Finish building the device description XML file, and add it to
+ // the virtual directory.
+ string descxml(
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<root xmlns=\"urn:schemas-upnp-org:device-1-0\">\n"
+ " <specVersion>\n"
+ " <major>1</major>\n"
+ " <minor>1</minor>\n"
+ " </specVersion>\n"
+ " <device>\n"
+ );
+ descxml += myxml.propsxml;
+ descxml += " <UDN>" + deviceId + "</UDN>\n";
+ descxml += " <serviceList>\n";
+ descxml += myxml.servicexml;
+ descxml += " </serviceList>\n";
+ if (!embedxml.empty()) {
+ descxml += " <devicelist>\n";
+ for (const auto& entry : embedxml) {
+ descxml += " <device>\n";
+ descxml += entry.second.propsxml;
+ descxml += " <UDN>" + entry.first + "</UDN>\n";
+ descxml += " <serviceList>\n";
+ descxml += entry.second.servicexml;
+ descxml += " </serviceList>\n";
+ descxml += " </device>\n";
+ }
+ descxml += "</devicelist>\n";
+ }
+
+ descxml += " </device>\n</root>\n";
+
+ VirtualDir* theVD = VirtualDir::getVirtualDir();
+ if (nullptr != theVD) {
+ theVD->addFile(devsubd, "description.xml", descxml, "application/xml");
+ } else {
+ LOGERR("UpnpDevice: can't start: no VirtualDir??\n");
+ return false;
+ }
+
+ // Tell the pupnp lib to serve the description.
+ string host(UpnpGetServerIpAddress());
+ unsigned short port = UpnpGetServerPort();
int ret;
- if (!description.empty()) {
- // Description is empty for embedded devices
- if ((ret = lib->setupWebServer(description, &dvh)) != 0) {
- LOGFAT("UpnpDevice: libupnp can't start service. Err " <<
- ret << endl);
+ string url = ipv4tostrurl(host, port) + devsubd + "description.xml";
+ if ((ret = lib->setupWebServer(url, &dvh)) != 0) {
+ LOGERR("UpnpDevice: libupnp can't start service. Err " << ret <<
+ endl);
+ return false;
+ }
+
+ if ((ret = UpnpSendAdvertisement(dvh, expiretime)) != 0) {
+ LOGERR("UpnpDevice::Internal::start(): sendAvertisement failed: " <<
+ lib->errAsString("UpnpDevice: UpnpSendAdvertisement", ret) <<
+ endl);
+ return false;
+ }
+ return true;
+}
+
+
+bool UpnpDevice::addVFile(const string& name, const string& contents,
+ const std::string& mime, string& path)
+{
+ VirtualDir* theVD = VirtualDir::getVirtualDir();
+ if (theVD) {
+ if (!theVD->addFile(m->devsubd, name, contents, mime)) {
return false;
}
- }
-
- if ((ret = UpnpSendAdvertisement(dvh, expiretime)) != 0) {
- LOGERR(lib->errAsString("UpnpDevice: UpnpSendAdvertisement", ret)
- << endl);
+ path = m->devsubd + name;
+ return true;
+ } else {
return false;
}
- return true;
}
// Main libupnp callback: use the device id and call the right device
-int UpnpDevice::InternalStatic::sCallBack(Upnp_EventType et, void* evp,
+int UpnpDevice::InternalStatic::sCallBack(Upnp_EventType et, CBCONST void* evp,
void*)
{
//LOGDEB("UpnpDevice::sCallBack" << endl);
@@ -257,15 +366,16 @@
string deviceid;
switch (et) {
case UPNP_CONTROL_ACTION_REQUEST:
- deviceid = ((struct Upnp_Action_Request *)evp)->DevUDN;
+ deviceid = UpnpActionRequest_get_DevUDN_cstr((UpnpActionRequest *)evp);
break;
case UPNP_CONTROL_GET_VAR_REQUEST:
- deviceid = ((struct Upnp_State_Var_Request *)evp)->DevUDN;
+ deviceid=UpnpStateVarRequest_get_DevUDN_cstr((UpnpStateVarRequest *)evp);
break;
case UPNP_EVENT_SUBSCRIPTION_REQUEST:
- deviceid = ((struct Upnp_Subscription_Request*)evp)->UDN;
+ deviceid =
+ UpnpSubscriptionRequest_get_UDN_cstr((UpnpSubscriptionRequest*)evp);
break;
default:
@@ -308,36 +418,38 @@
return servit;
}
-int UpnpDevice::Internal::callBack(Upnp_EventType et, void* evp)
+int UpnpDevice::Internal::callBack(Upnp_EventType et, const void* evp)
{
switch (et) {
case UPNP_CONTROL_ACTION_REQUEST:
{
- struct Upnp_Action_Request *act = (struct Upnp_Action_Request *)evp;
-
- LOGDEB("UPNP_CONTROL_ACTION_REQUEST: " << act->ActionName <<
- ". Params: " << ixmlwPrintDoc(act->ActionRequest) << endl);
+ UpnpActionRequest *act = (UpnpActionRequest *)evp;
+
+ LOGDEB("UPNP_CONTROL_ACTION_REQUEST: " <<
+ UpnpActionRequest_get_ActionName_cstr(act) << ". Params: " <<
+ ixmlwPrintDoc(UpnpActionRequest_get_ActionRequest(act)) << endl);
std::unordered_map<string, UpnpService*>::const_iterator servit =
- findService(act->ServiceID);
+ findService(UpnpActionRequest_get_ServiceID_cstr(act));
if (servit == servicemap.end()) {
return UPNP_E_INVALID_PARAM;
}
- SoapOutgoing dt(servit->second->getServiceType(), act->ActionName);
+ string actname{UpnpActionRequest_get_ActionName_cstr(act)};
+ SoapOutgoing dt(servit->second->getServiceType(), actname);
{
std::unique_lock<std::mutex> lock(devlock);
std::unordered_map<std::string, soapfun>::iterator callit =
- calls.find(string(act->ActionName) + string(act->ServiceID));
+ calls.find(actname + UpnpActionRequest_get_ServiceID_cstr(act));
if (callit == calls.end()) {
- LOGINF("UpnpDevice: No such action: " <<
- act->ActionName << endl);
+ LOGINF("UpnpDevice: No such action: " << actname << endl);
return UPNP_E_INVALID_PARAM;
}
SoapIncoming sc;
- if (!sc.decode(act->ActionName, act->ActionRequest)) {
+ if (!sc.decode(actname.c_str(),
+ UpnpActionRequest_get_ActionRequest(act))) {
LOGERR("Error decoding Action call arguments" << endl);
return UPNP_E_INVALID_PARAM;
}
@@ -346,10 +458,16 @@
int ret = callit->second(sc, dt);
if (ret != UPNP_E_SUCCESS) {
if (ret > 0) {
+#if UPNP_VERSION_MINOR < 8
act->ErrCode = ret;
- strncpy(act->ErrStr, servit->second->errString(ret).c_str(),
- LINE_SIZE-1);
+ strncpy(act->ErrStr,
+ servit->second->errString(ret).c_str(), LINE_SIZE-1);
act->ErrStr[LINE_SIZE-1] = 0;
+#else
+ UpnpActionRequest_set_ErrCode(act, ret);
+ UpnpActionRequest_strcpy_ErrStr(
+ act, servit->second->errString(ret).c_str());
+#endif
}
LOGERR("UpnpDevice: Action failed: " << sc.getName() <<
" code " << ret << endl);
@@ -358,9 +476,15 @@
}
// Encode result data
+#if UPNP_VERSION_MINOR < 8
+ act->ErrCode = UPNP_E_SUCCESS;
act->ActionResult = dt.buildSoapBody();
-
- //LOGDEB("Response data: " << ixmlwPrintDoc(act->ActionResult) << endl);
+#else
+ UpnpActionRequest_set_ErrCode(act, UPNP_E_SUCCESS);
+ UpnpActionRequest_set_ActionResult(act, dt.buildSoapBody());
+#endif
+ LOGDEB1("Response data: " <<
+ ixmlwPrintDoc(UpnpActionRequest_get_ActionResult(act)) << endl);
return UPNP_E_SUCCESS;
}
@@ -370,19 +494,19 @@
// Note that the "Control: query for variable" action is
// deprecated (upnp arch v1), and we should never get these.
{
- struct Upnp_State_Var_Request *act =
- (struct Upnp_State_Var_Request *)evp;
- LOGDEB("UPNP_CONTROL_GET_VAR__REQUEST?: " << act->StateVarName << endl);
+ UpnpStateVarRequest *act = (UpnpStateVarRequest *)evp;
+ LOGDEB("UPNP_CONTROL_GET_VAR__REQUEST?: " <<
+ UpnpStateVarRequest_get_StateVarName_cstr(act) << endl);
}
break;
case UPNP_EVENT_SUBSCRIPTION_REQUEST:
{
- struct Upnp_Subscription_Request *act =
- (struct Upnp_Subscription_Request*)evp;
- LOGDEB("UPNP_EVENT_SUBSCRIPTION_REQUEST: " << act->ServiceId << endl);
-
- auto servit = findService(act->ServiceId);
+ UpnpSubscriptionRequest *act = (UpnpSubscriptionRequest*)evp;
+ LOGDEB("UPNP_EVENT_SUBSCRIPTION_REQUEST: " <<
+ UpnpSubscriptionRequest_get_ServiceId_cstr(act) << endl);
+
+ auto servit=findService(UpnpSubscriptionRequest_get_ServiceId_cstr(act));
if (servit == servicemap.end()) {
return UPNP_E_INVALID_PARAM;
}
@@ -397,10 +521,11 @@
vector<const char *> cnames, cvalues;
vectorstoargslists(names, values, qvalues, cnames, cvalues);
- int ret =
- UpnpAcceptSubscription(dvh, act->UDN, act->ServiceId,
- &cnames[0], &cvalues[0],
- int(cnames.size()), act->Sid);
+ int ret = UpnpAcceptSubscription(
+ dvh, UpnpSubscriptionRequest_get_UDN_cstr(act),
+ UpnpSubscriptionRequest_get_ServiceId_cstr(act),
+ &cnames[0], &cvalues[0], int(cnames.size()),
+ UpnpSubscriptionRequest_get_SID_cstr(act));
if (ret != UPNP_E_SUCCESS) {
LOGERR(lib->errAsString(
"UpnpDevice::callBack: UpnpAcceptSubscription", ret) <<
@@ -419,17 +544,65 @@
return UPNP_E_INVALID_PARAM;
}
-void UpnpDevice::addService(UpnpService *serv, const std::string& serviceId)
-{
+bool UpnpDevice::addService(UpnpService *serv)
+{
+ const string& serviceId = serv->getServiceId();
LOGDEB("UpnpDevice::addService: [" << serviceId << "]\n");
std::unique_lock<std::mutex> lock(m->devlock);
+ string* propsxml;
+ string* servicexml;
+ if (nullptr == m->rootdev) {
+ propsxml = &m->myxml.propsxml;
+ servicexml = &m->myxml.servicexml;
+ } else {
+ auto it = m->rootdev->m->embedxml.find(m->deviceId);
+ if (it == m->rootdev->m->embedxml.end()) {
+ LOGERR("UpnpDevice::addservice: my Id " << m->deviceId <<
+ " not found in root dev " << m->rootdev->m->deviceId << endl);
+ return false;
+ }
+ propsxml = &it->second.propsxml;
+ servicexml = &it->second.servicexml;
+ }
+
+ if (propsxml->empty()) {
+ // Retrieve the XML fragment with friendlyname etc.
+ if (!readLibFile("", *propsxml)) {
+ LOGERR("UpnpDevice::start: Could not read description XML props\n");
+ return false;
+ }
+ }
+
m->servicemap[serviceId] = serv;
vector<string>::iterator it =
std::find(m->serviceids.begin(), m->serviceids.end(), serviceId);
if (it != m->serviceids.end())
m->serviceids.erase(it);
m->serviceids.push_back(serviceId);
+
+ string servnick = neutchars(serv->getServiceType(), replchars, '-');
+
+ // Add the service description to the virtual directory
+ string scpdxml;
+ string xmlfn = serv->getXMLFn();
+ VirtualDir* theVD = VirtualDir::getVirtualDir();
+ if (!readLibFile(xmlfn, scpdxml)) {
+ LOGERR("UpnpDevice::addService: could not retrieve service definition"
+ "file: nm: [" << xmlfn << "]\n");
+ } else {
+ theVD->addFile(m->devsubd, servnick + ".xml",
+ scpdxml, "application/xml");
+ }
+ *servicexml +=
+ string("<service>\n") +
+ " <serviceType>" + serv->getServiceType() + "</serviceType>\n" +
+ " <serviceId>" + serv->getServiceId() + "</serviceId>\n" +
+ " <SCPDURL>" + m->devsubd + servnick + ".xml</SCPDURL>\n" +
+ " <controlURL>" + m->devsubd + "ctl-" + servnick + "</controlURL>\n" +
+ " <eventSubURL>" + m->devsubd + "evt-" + servnick + "</eventSubURL>\n" +
+ "</service>\n";
+ return true;
}
void UpnpDevice::forgetService(const std::string& serviceId)
@@ -461,8 +634,9 @@
const vector<string>& names,
const vector<string>& values)
{
-// LOGDEB1("UpnpDevice::notifyEvent " << serviceId << " " <<
-// (names.empty() ? "Empty names??" : names[0]) << endl);
+ LOGDEB1("UpnpDevice::notifyEvent: deviceId " << deviceId << " serviceId " <<
+ serviceId << " nm[0] " << (names.empty()?"Empty names??" : names[0])
+ << endl);
stringstream ss;
for (unsigned int i = 0; i < names.size() && i < values.size(); i++) {
ss << names[i] << "=" << values[i] << " ";
@@ -479,15 +653,21 @@
serviceId.c_str(), &cnames[0], &cvalues[0],
int(cnames.size()));
if (ret != UPNP_E_SUCCESS) {
- LOGERR(lib->errAsString("UpnpDevice::notifyEvent: id", ret) <<
+ LOGERR("UpnpDevice::notifyEvent: " << lib->errAsString("UpnpNotify", ret) <<
" for " << serviceId << endl);
}
}
-int timespec_diffms(const struct timespec& old, const struct timespec& recent)
+static int timespec_diffms(const struct timespec& old,
+ const struct timespec& recent)
{
return (recent.tv_sec - old.tv_sec) * 1000 +
(recent.tv_nsec - old.tv_nsec) / (1000 * 1000);
+}
+
+void UpnpDevice::startloop()
+{
+ m->loopthread = std::thread(&UpnpDevice::eventloop, this);
}
// Loop on services, and poll each for changed data. Generate event
@@ -618,24 +798,23 @@
Internal(bool noevs)
: noevents(noevs), dev(0) {
}
+ string serviceType;
+ string serviceId;
+ string xmlfn;
bool noevents;
UpnpDevice *dev;
};
-UpnpService::UpnpService(const std::string& stp,
- const std::string& sid, UpnpDevice *dev)
- : m_serviceType(stp), m_serviceId(sid), m(new Internal(false))
+UpnpService::UpnpService(
+ const std::string& stp, const std::string& sid, const std::string& xmlfn,
+ UpnpDevice *dev, bool noevs)
+ : m(new Internal(noevs))
{
m->dev = dev;
- dev->addService(this, sid);
-}
-
-UpnpService::UpnpService(const std::string& stp,
- const std::string& sid, UpnpDevice *dev, bool noevs)
- : m_serviceType(stp), m_serviceId(sid), m(new Internal(noevs))
-{
- m->dev = dev;
- dev->addService(this, sid);
+ m->serviceType = stp;
+ m->serviceId = sid;
+ m->xmlfn = xmlfn;
+ dev->addService(this);
}
UpnpDevice *UpnpService::getDevice()
@@ -651,7 +830,7 @@
{
if (m) {
if (m->dev)
- m->dev->forgetService(m_serviceId);
+ m->dev->forgetService(m->serviceId);
delete m;
m = 0;
}
@@ -670,12 +849,17 @@
const std::string& UpnpService::getServiceType() const
{
- return m_serviceType;
+ return m->serviceType;
}
const std::string& UpnpService::getServiceId() const
{
- return m_serviceId;
+ return m->serviceId;
+}
+
+const std::string& UpnpService::getXMLFn() const
+{
+ return m->xmlfn;
}
const std::string UpnpService::errString(int error) const