SyncML server: find configuration for client automatically (MB#7710)
When the syncevo-dbus-server receives a SyncML message as initial data from a transport stub, it peeks into the data with the help of the Synthesis server engine and our SyncEvolution_Session_CheckDevice() and extracts the LocURI = device ID of the client. This value is guaranteed to be in the initial SyncML message. Then it searches for a peer configuration (still called "server configuration" in the current code) which has the same ID in its remoteDeviceID property and uses that configuration. Instantiating SyncContext and Synthesis engine multiple times is a bit tricky. The context for the SynthesisDBPlugin currently has to be passed via a global variable, SyncContext::m_activeContext. SyncContext::analyzeSyncMLMessage() temporarily replaces that with the help of a new sentinal class SwapContext, which ensures that the previous context is restored when leaving the function. Some common code was moved around for this (SyncContext::initEngine()). The "default config" parameter in syncevo-http-server.py was removed because it is no longer needed. The possibility to select some kind of config context via the path below the sync URL still exists. This is passed to syncevo-dbus-server via the "config" peer attribute. It has no effect there at the moment. TestConnection.testStartSync() in test-dbus.py covers this kind of config selection. To run it, a peer configuration with remoteDeviceID "sc-api-nat" must exist.
This commit is contained in:
parent
ab6a01f680
commit
15b1de77b8
|
@ -2064,10 +2064,39 @@ void Connection::process(const Caller_t &caller,
|
|||
message_type == TransportAgent::m_contentTypeSyncWBXML) {
|
||||
// run a new SyncML session as server
|
||||
serverMode = true;
|
||||
if (m_peer.find("config") == m_peer.end()) {
|
||||
throw runtime_error("must choose config in Server.Connect()");
|
||||
if (m_peer.find("config") == m_peer.end() &&
|
||||
!m_peer["config"].empty()) {
|
||||
SE_LOG_DEBUG(NULL, NULL, "ignoring pre-chosen config '%s'",
|
||||
m_peer["config"].c_str());
|
||||
}
|
||||
|
||||
// peek into the data to extract the locURI = device ID,
|
||||
// then use it to find the configuration
|
||||
SyncContext::SyncMLMessageInfo info;
|
||||
info = SyncContext::analyzeSyncMLMessage(reinterpret_cast<const char *>(message.second),
|
||||
message.first,
|
||||
message_type);
|
||||
if (info.m_deviceID.empty()) {
|
||||
// TODO: proper exception
|
||||
throw runtime_error("could not extract LocURI=deviceID from initial message");
|
||||
}
|
||||
BOOST_FOREACH(const StringPair &entry,
|
||||
SyncConfig::getServers()) {
|
||||
SyncConfig peer(entry.first);
|
||||
if (info.m_deviceID == peer.getRemoteDevID()) {
|
||||
config = entry.first;
|
||||
SE_LOG_DEBUG(NULL, NULL, "matched %s against config %s (%s)",
|
||||
info.toString().c_str(),
|
||||
entry.first.c_str(),
|
||||
entry.second.c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (config.empty()) {
|
||||
// TODO: proper exception
|
||||
throw runtime_error(string("no configuration found for ") +
|
||||
info.toString());
|
||||
}
|
||||
config = m_peer["config"];
|
||||
} else {
|
||||
throw runtime_error("message type not supported for starting a sync");
|
||||
}
|
||||
|
|
|
@ -1515,7 +1515,10 @@ SharedEngine SyncContext::createEngine()
|
|||
sysync::DBG_PLUGIN_EXOT);
|
||||
|
||||
SharedKey configvars = engine.OpenKeyByPath(SharedKey(), "/configvars");
|
||||
string logdir = m_sourceListPtr->getLogdir();
|
||||
string logdir;
|
||||
if (m_sourceListPtr) {
|
||||
logdir = m_sourceListPtr->getLogdir();
|
||||
}
|
||||
engine.SetStrValue(configvars, "defout_path",
|
||||
logdir.size() ? logdir : "/dev/null");
|
||||
engine.SetStrValue(configvars, "conferrpath", "console");
|
||||
|
@ -1543,6 +1546,72 @@ void SyncContext::initServer(const std::string &sessionID,
|
|||
|
||||
}
|
||||
|
||||
struct SyncContext::SyncMLMessageInfo
|
||||
SyncContext::analyzeSyncMLMessage(const char *data, size_t len,
|
||||
const std::string &messageType)
|
||||
{
|
||||
SyncContext sync("", false);
|
||||
SwapContext syncSentinel(&sync);
|
||||
SourceList sourceList(sync, false);
|
||||
sourceList.setLogLevel(SourceList::LOGGING_SUMMARY);
|
||||
m_sourceListPtr = &sourceList;
|
||||
sync.initServer("", SharedBuffer(), "");
|
||||
SwapEngine swapengine(sync);
|
||||
sync.initEngine(false);
|
||||
|
||||
sysync::TEngineProgressInfo progressInfo;
|
||||
sysync::uInt16 stepCmd = sysync::STEPCMD_GOTDATA;
|
||||
SharedSession session = sync.m_engine.OpenSession(sync.m_sessionID);
|
||||
SessionSentinel sessionSentinel(sync, session);
|
||||
|
||||
sync.m_engine.WriteSyncMLBuffer(session, data, len);
|
||||
SharedKey sessionKey = sync.m_engine.OpenSessionKey(session);
|
||||
sync.m_engine.SetStrValue(sessionKey,
|
||||
"contenttype",
|
||||
messageType);
|
||||
|
||||
// analyze main loop: runs until SessionStep() signals reply or error.
|
||||
// Will call our SynthesisDBPlugin callbacks, most importantly
|
||||
// SyncEvolution_Session_CheckDevice(), which records the device ID
|
||||
// for us.
|
||||
do {
|
||||
sync.m_engine.SessionStep(session, stepCmd, &progressInfo);
|
||||
switch (stepCmd) {
|
||||
case sysync::STEPCMD_OK:
|
||||
case sysync::STEPCMD_PROGRESS:
|
||||
stepCmd = sysync::STEPCMD_STEP;
|
||||
break;
|
||||
default:
|
||||
// whatever it is, cannot proceed
|
||||
break;
|
||||
}
|
||||
} while (stepCmd == sysync::STEPCMD_STEP);
|
||||
|
||||
SyncMLMessageInfo info;
|
||||
info.m_deviceID = sync.getSyncDeviceID();
|
||||
return info;
|
||||
}
|
||||
|
||||
void SyncContext::initEngine(bool logXML)
|
||||
{
|
||||
string xml, configname;
|
||||
getConfigXML(xml, configname);
|
||||
try {
|
||||
m_engine.InitEngineXML(xml.c_str());
|
||||
} catch (const BadSynthesisResult &ex) {
|
||||
SE_LOG_ERROR(NULL, NULL,
|
||||
"internal error, invalid XML configuration (%s):\n%s",
|
||||
m_sourceListPtr && !m_sourceListPtr->empty() ?
|
||||
"with datastores" :
|
||||
"without datastores",
|
||||
xml.c_str());
|
||||
throw;
|
||||
}
|
||||
if (logXML) {
|
||||
SE_LOG_DEV(NULL, NULL, "Full XML configuration:\n%s", xml.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
SyncMLStatus SyncContext::sync(SyncReport *report)
|
||||
{
|
||||
SyncMLStatus status = STATUS_OK;
|
||||
|
@ -1558,9 +1627,9 @@ SyncMLStatus SyncContext::sync(SyncReport *report)
|
|||
getPrintChanges() ? SourceList::LOGGING_FULL :
|
||||
SourceList::LOGGING_SUMMARY);
|
||||
|
||||
SwapContext syncSentinel(this);
|
||||
try {
|
||||
m_sourceListPtr = &sourceList;
|
||||
m_activeContext = this;
|
||||
|
||||
if (getenv("SYNCEVOLUTION_GNUTLS_DEBUG")) {
|
||||
// Enable libgnutls debugging without creating a hard dependency on it,
|
||||
|
@ -1599,16 +1668,7 @@ SyncMLStatus SyncContext::sync(SyncReport *report)
|
|||
// create a Synthesis engine, used purely for logging purposes
|
||||
// at this time
|
||||
SwapEngine swapengine(*this);
|
||||
string xml, configname;
|
||||
getConfigXML(xml, configname);
|
||||
try {
|
||||
m_engine.InitEngineXML(xml.c_str());
|
||||
} catch (const BadSynthesisResult &ex) {
|
||||
SE_LOG_ERROR(NULL, NULL,
|
||||
"internal error, invalid XML configuration (without datastores):\n%s",
|
||||
xml.c_str());
|
||||
throw;
|
||||
}
|
||||
initEngine(false);
|
||||
|
||||
try {
|
||||
// dump some summary information at the beginning of the log
|
||||
|
@ -1697,7 +1757,6 @@ SyncMLStatus SyncContext::sync(SyncReport *report)
|
|||
}
|
||||
|
||||
m_sourceListPtr = NULL;
|
||||
m_activeContext = NULL;
|
||||
return status;
|
||||
}
|
||||
|
||||
|
@ -1713,16 +1772,7 @@ SyncMLStatus SyncContext::doSync()
|
|||
|
||||
// re-init engine with all sources configured
|
||||
string xml, configname;
|
||||
getConfigXML(xml, configname);
|
||||
try {
|
||||
m_engine.InitEngineXML(xml.c_str());
|
||||
} catch (const BadSynthesisResult &ex) {
|
||||
SE_LOG_ERROR(NULL, NULL,
|
||||
"internal error, invalid XML configuration (with datastores):\n%s",
|
||||
xml.c_str());
|
||||
throw;
|
||||
}
|
||||
SE_LOG_DEV(NULL, NULL, "Full XML configuration:\n%s", xml.c_str());
|
||||
initEngine(true);
|
||||
|
||||
SharedKey targets;
|
||||
SharedKey target;
|
||||
|
|
|
@ -78,6 +78,7 @@ class SyncContext : public SyncConfig, public ConfigUserInterface {
|
|||
std::string m_sessionID;
|
||||
SharedBuffer m_initialMessage;
|
||||
string m_initialMessageType;
|
||||
string m_syncDeviceID;
|
||||
|
||||
/**
|
||||
* flags for suspend and abort
|
||||
|
@ -92,9 +93,20 @@ class SyncContext : public SyncConfig, public ConfigUserInterface {
|
|||
|
||||
/**
|
||||
* a pointer to the active SyncContext instance if one exists;
|
||||
* set by sync()
|
||||
* set by sync() and/or SwapContext
|
||||
*/
|
||||
static SyncContext *m_activeContext;
|
||||
class SwapContext {
|
||||
SyncContext *m_oldContext;
|
||||
public:
|
||||
SwapContext(SyncContext *newContext) :
|
||||
m_oldContext(SyncContext::m_activeContext) {
|
||||
SyncContext::m_activeContext = newContext;
|
||||
}
|
||||
~SwapContext() {
|
||||
SyncContext::m_activeContext = m_oldContext;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Connection to the Synthesis engine. Always valid in a
|
||||
|
@ -143,6 +155,10 @@ class SyncContext : public SyncConfig, public ConfigUserInterface {
|
|||
bool getMustAuthenticate() { return m_mustAuthenticate; }
|
||||
void setMustAuthenticate(bool mustAuthenticate) { m_mustAuthenticate = mustAuthenticate; }
|
||||
|
||||
/** only for server: device ID of peer */
|
||||
void setSyncDeviceID(const std::string &deviceID) { m_syncDeviceID = deviceID; }
|
||||
std::string getSyncDeviceID() const { return m_syncDeviceID; }
|
||||
|
||||
static SuspendFlags& getSuspendFlags() {return s_flags;}
|
||||
|
||||
/**
|
||||
|
@ -167,6 +183,25 @@ class SyncContext : public SyncConfig, public ConfigUserInterface {
|
|||
*/
|
||||
SyncMLStatus sync(SyncReport *report = NULL);
|
||||
|
||||
/** result of analyzeSyncMLMessage() */
|
||||
struct SyncMLMessageInfo {
|
||||
std::string m_deviceID;
|
||||
|
||||
/** a string representation of the whole structure for debugging */
|
||||
std::string toString() { return std::string("deviceID ") + m_deviceID; }
|
||||
};
|
||||
|
||||
/**
|
||||
* Instead or executing a sync, analyze the initial message
|
||||
* without changing any local data. Returns once the LocURI =
|
||||
* device ID of the client is known.
|
||||
*
|
||||
* @return device ID, empty if not in data
|
||||
*/
|
||||
static SyncMLMessageInfo
|
||||
analyzeSyncMLMessage(const char *data, size_t len,
|
||||
const std::string &messageType);
|
||||
|
||||
/**
|
||||
* Convenience function, to be called inside a catch() block of
|
||||
* (or for) the sync.
|
||||
|
@ -523,6 +558,11 @@ class SyncContext : public SyncConfig, public ConfigUserInterface {
|
|||
virtual bool checkForSuspend() { return (s_flags.state == SuspendFlags::CLIENT_SUSPEND);}
|
||||
|
||||
private:
|
||||
/**
|
||||
* generate XML configuration and (re)initialize engine with it
|
||||
*/
|
||||
void initEngine(bool logXML);
|
||||
|
||||
/**
|
||||
* the code common to init() and status():
|
||||
* populate source list with active sources and open
|
||||
|
|
|
@ -207,6 +207,7 @@ TSyError SyncEvolution_Session_CheckDevice( CContext sContext,
|
|||
}
|
||||
TSyError res = LOCERR_OK;
|
||||
|
||||
sc->setSyncDeviceID(aDeviceID);
|
||||
string id = sc->getRemoteDevID();
|
||||
if (id.empty()) {
|
||||
sc->setRemoteDevID(aDeviceID);
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
#! /usr/bin/python
|
||||
|
||||
'''Usage: syncevo-http-server.py <URL> <default config>
|
||||
Runs a SyncML HTTP server under the given base URL,
|
||||
using one specific configuration.'''
|
||||
'''Usage: syncevo-http-server.py <URL>
|
||||
Runs a SyncML HTTP server under the given base URL.'''
|
||||
|
||||
# use the same glib main loop in D-Bus and twisted
|
||||
from dbus.mainloop.glib import DBusGMainLoop
|
||||
|
@ -144,9 +143,8 @@ class SyncMLSession:
|
|||
class SyncMLPost(resource.Resource):
|
||||
isLeaf = True
|
||||
|
||||
def __init__(self, url, defaultconfig):
|
||||
def __init__(self, url):
|
||||
self.url = url
|
||||
self.defaultconfig = defaultconfig
|
||||
|
||||
def render_GET(self, request):
|
||||
return "<html>SyncEvolution SyncML Server</html>"
|
||||
|
@ -156,7 +154,7 @@ class SyncMLPost(resource.Resource):
|
|||
if config:
|
||||
config = config[0]
|
||||
else:
|
||||
config = self.defaultconfig
|
||||
config = ""
|
||||
type = request.getHeader('content-type')
|
||||
len = request.getHeader('content-length')
|
||||
sessionid = request.args.get('sessionid')
|
||||
|
@ -180,9 +178,8 @@ class SyncMLPost(resource.Resource):
|
|||
|
||||
def main():
|
||||
url = urlparse.urlparse(sys.argv[1])
|
||||
defaultconfig = sys.argv[2]
|
||||
root = resource.Resource()
|
||||
root.putChild(url.path[1:], SyncMLPost(url, defaultconfig))
|
||||
root.putChild(url.path[1:], SyncMLPost(url))
|
||||
site = server.Site(root)
|
||||
reactor.listenTCP(url.port, site)
|
||||
reactor.run()
|
||||
|
|
|
@ -319,7 +319,7 @@ class TestConnection(unittest.TestCase, DBusUtil):
|
|||
"""Tests Server.Connect(). Tests depend on getting one Abort signal to terminate."""
|
||||
|
||||
"""a real message sent to our own server, DevInf stripped"""
|
||||
message1 = '''<?xml version="1.0" encoding="UTF-8"?><SyncML xmlns='SYNCML:SYNCML1.2'><SyncHdr><VerDTD>1.2</VerDTD><VerProto>SyncML/1.2</VerProto><SessionID>255</SessionID><MsgID>1</MsgID><Target><LocURI>http://127.0.0.1:9000/syncevolution</LocURI></Target><Source><LocURI>sc-pim-117cdd45-3892-4a2d-ae62-4c98cb0f3eae</LocURI><LocName>test</LocName></Source><Cred><Meta><Format xmlns='syncml:metinf'>b64</Format><Type xmlns='syncml:metinf'>syncml:auth-md5</Type></Meta><Data>kHzMn3RWFGWSKeBpXicppQ==</Data></Cred><Meta><MaxMsgSize xmlns='syncml:metinf'>20000</MaxMsgSize><MaxObjSize xmlns='syncml:metinf'>4000000</MaxObjSize></Meta></SyncHdr><SyncBody><Alert><CmdID>1</CmdID><Data>200</Data><Item><Target><LocURI>addressbook</LocURI></Target><Source><LocURI>./addressbook</LocURI></Source><Meta><Anchor xmlns='syncml:metinf'><Last>20091105T092757Z</Last><Next>20091105T092831Z</Next></Anchor><MaxObjSize xmlns='syncml:metinf'>4000000</MaxObjSize></Meta></Item></Alert><Final/></SyncBody></SyncML>'''
|
||||
message1 = '''<?xml version="1.0" encoding="UTF-8"?><SyncML xmlns='SYNCML:SYNCML1.2'><SyncHdr><VerDTD>1.2</VerDTD><VerProto>SyncML/1.2</VerProto><SessionID>255</SessionID><MsgID>1</MsgID><Target><LocURI>http://127.0.0.1:9000/syncevolution</LocURI></Target><Source><LocURI>sc-api-nat</LocURI><LocName>test</LocName></Source><Cred><Meta><Format xmlns='syncml:metinf'>b64</Format><Type xmlns='syncml:metinf'>syncml:auth-md5</Type></Meta><Data>kHzMn3RWFGWSKeBpXicppQ==</Data></Cred><Meta><MaxMsgSize xmlns='syncml:metinf'>20000</MaxMsgSize><MaxObjSize xmlns='syncml:metinf'>4000000</MaxObjSize></Meta></SyncHdr><SyncBody><Alert><CmdID>1</CmdID><Data>200</Data><Item><Target><LocURI>addressbook</LocURI></Target><Source><LocURI>./addressbook</LocURI></Source><Meta><Anchor xmlns='syncml:metinf'><Last>20091105T092757Z</Last><Next>20091105T092831Z</Next></Anchor><MaxObjSize xmlns='syncml:metinf'>4000000</MaxObjSize></Meta></Item></Alert><Final/></SyncBody></SyncML>'''
|
||||
|
||||
def setUp(self):
|
||||
DBusUtil.__init__(self)
|
||||
|
@ -331,7 +331,6 @@ class TestConnection(unittest.TestCase, DBusUtil):
|
|||
|
||||
def getConnection(self):
|
||||
conpath = self.server.Connect({'description': 'test-dbus.py',
|
||||
'config': 'syncevolution_server',
|
||||
'transport': 'dummy'},
|
||||
False,
|
||||
"")
|
||||
|
|
Loading…
Reference in New Issue