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:
Patrick Ohly 2009-11-06 11:43:55 +01:00
parent ab6a01f680
commit 15b1de77b8
6 changed files with 153 additions and 37 deletions

View File

@ -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");
}

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

@ -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()

View File

@ -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,
"")