* add setSinkVolume to telnetserver
[profile/ivi/audiomanager.git] / AudioManagerDaemon / src / CAmRoutingSender.cpp
1 /**
2  * Copyright (C) 2012, BMW AG
3  *
4  * This file is part of GENIVI Project AudioManager.
5  *
6  * Contributions are licensed to the GENIVI Alliance under one or more
7  * Contribution License Agreements.
8  *
9  * \copyright
10  * This Source Code Form is subject to the terms of the
11  * Mozilla Public License, v. 2.0. If a  copy of the MPL was not distributed with
12  * this file, You can obtain one at http://mozilla.org/MPL/2.0/.
13  *
14  *
15  * \author Christian Mueller, christian.ei.mueller@bmw.de BMW 2011,2012
16  *
17  * \file CAmRoutingSender.cpp
18  * For further information see http://www.genivi.org/.
19  *
20  */
21
22 #include "CAmRoutingSender.h"
23 #include <utility>
24 #include <dirent.h>
25 #include <dlfcn.h>
26 #include <cassert>
27 #include <iostream>
28 #include <sstream>
29 #include "CAmRoutingReceiver.h"
30 #include "TAmPluginTemplate.h"
31 #include "shared/CAmDltWrapper.h"
32
33 namespace am
34 {
35
36 #define REQUIRED_INTERFACE_VERSION_MAJOR 1  //!< major interface version. All versions smaller than this will be rejected
37 #define REQUIRED_INTERFACE_VERSION_MINOR 0 //!< minor interface version. All versions smaller than this will be rejected
38
39 CAmRoutingSender::CAmRoutingSender(const std::vector<std::string>& listOfPluginDirectories) :
40         mHandleCount(0), //
41         mlistActiveHandles(), //
42         mListInterfaces(), //
43         mMapConnectionInterface(), //
44         mMapCrossfaderInterface(), //
45         mMapDomainInterface(), //
46         mMapSinkInterface(), //
47         mMapSourceInterface(), //
48         mMapHandleInterface(), //
49         mpRoutingReceiver()
50 {
51     std::vector<std::string> sharedLibraryNameList;
52     std::vector<std::string>::const_iterator dirIter = listOfPluginDirectories.begin();
53     std::vector<std::string>::const_iterator dirIterEnd = listOfPluginDirectories.end();
54
55     // search communicator plugins in configured directories
56     for (; dirIter < dirIterEnd; ++dirIter)
57     {
58         const char* directoryName = dirIter->c_str();
59         logInfo("Searching for HookPlugins in", directoryName);
60         DIR *directory = opendir(directoryName);
61
62         if (!directory)
63         {
64             logError("RoutingSender::RoutingSender Error opening directory: ", directoryName);
65             continue;
66         }
67
68         // iterate content of directory
69         struct dirent *itemInDirectory = 0;
70         while ((itemInDirectory = readdir(directory)))
71         {
72             unsigned char entryType = itemInDirectory->d_type;
73             std::string entryName = itemInDirectory->d_name;
74
75             bool regularFile = (entryType == DT_REG || entryType == DT_LNK);
76             bool sharedLibExtension = ("so" == entryName.substr(entryName.find_last_of(".") + 1));
77
78             if (regularFile && sharedLibExtension)
79             {
80                 logInfo("RoutingSender::RoutingSender adding file: ", entryName);
81                 std::string name(directoryName);
82                 sharedLibraryNameList.push_back(name + "/" + entryName);
83             }
84             else
85             {
86                 logInfo("RoutingSender::RoutingSender PluginSearch ignoring file :", entryName);
87             }
88         }
89
90         closedir(directory);
91     }
92
93     // iterate all communicator plugins and start them
94     std::vector<std::string>::iterator iter = sharedLibraryNameList.begin();
95     std::vector<std::string>::iterator iterEnd = sharedLibraryNameList.end();
96
97     for (; iter != iterEnd; ++iter)
98     {
99         logInfo("RoutingSender::RoutingSender try loading: ", *iter);
100
101         IAmRoutingSend* (*createFunc)();
102         void* tempLibHandle = NULL;
103         createFunc = getCreateFunction<IAmRoutingSend*()>(*iter, tempLibHandle);
104
105         if (!createFunc)
106         {
107             logError("RoutingSender::RoutingSender Entry point of RoutingPlugin not found");
108             continue;
109         }
110
111         IAmRoutingSend* router = createFunc();
112
113         if (!router)
114         {
115             logError("RoutingSender::RoutingSender RoutingPlugin initialization failed. Entry Function not callable");
116             continue;
117         }
118
119         InterfaceNamePairs routerInterface;
120         routerInterface.routingInterface = router;
121
122         //check libversion
123         std::string version;
124         router->getInterfaceVersion(version);
125         uint16_t minorVersion, majorVersion;
126         std::istringstream(version.substr(0, 1)) >> majorVersion;
127         std::istringstream(version.substr(2, 1)) >> minorVersion;
128         if (majorVersion < REQUIRED_INTERFACE_VERSION_MAJOR || ((majorVersion == REQUIRED_INTERFACE_VERSION_MAJOR) && (minorVersion > REQUIRED_INTERFACE_VERSION_MINOR)))
129         {
130             logInfo("RoutingPlugin initialization failed. Version of Interface to old");
131             continue;
132         }
133
134         //here, the busname is saved together with the interface. Later The domains will register with the name and sinks, sources etc with the domain....
135         router->returnBusName(routerInterface.busName);
136         assert(!routerInterface.busName.empty());
137         mListInterfaces.push_back(routerInterface);
138         mListLibraryHandles.push_back(tempLibHandle);
139     }
140 }
141
142 CAmRoutingSender::~CAmRoutingSender()
143 {
144     unloadLibraries();
145     HandlesMap::iterator it = mlistActiveHandles.begin();
146
147     //clean up heap if existent
148     for (; it != mlistActiveHandles.end(); ++it)
149     {
150         if (it->first.handleType == H_SETSINKSOUNDPROPERTIES || it->first.handleType == H_SETSOURCESOUNDPROPERTIES)
151         {
152             delete it->second.soundProperties;
153         }
154     }
155 }
156
157 am_Error_e CAmRoutingSender::startupInterfaces(CAmRoutingReceiver *iRoutingReceiver)
158 {
159     mpRoutingReceiver = iRoutingReceiver;
160     am_Error_e returnError = E_OK;
161
162     std::vector<InterfaceNamePairs>::iterator iter = mListInterfaces.begin();
163     std::vector<InterfaceNamePairs>::iterator iterEnd = mListInterfaces.end();
164     for (; iter < iterEnd; ++iter)
165     {
166         am_Error_e error = (*iter).routingInterface->startupInterface(iRoutingReceiver);
167         if (error != E_OK)
168         {
169             returnError = error;
170         }
171     }
172     return (returnError);
173 }
174
175 am_Error_e CAmRoutingSender::asyncAbort(const am_Handle_s& handle)
176 {
177     HandleInterfaceMap::iterator iter = mMapHandleInterface.begin();
178     iter = mMapHandleInterface.find(handle.handle);
179     if (iter != mMapHandleInterface.end())
180     {
181         return (iter->second->asyncAbort(handle));
182     }
183
184     return (E_NON_EXISTENT);
185 }
186
187 am_Error_e CAmRoutingSender::asyncConnect(am_Handle_s& handle, const am_connectionID_t connectionID, const am_sourceID_t sourceID, const am_sinkID_t sinkID, const am_ConnectionFormat_e connectionFormat)
188 {
189     am_handleData_c handleData;
190     SinkInterfaceMap::iterator iter = mMapSinkInterface.begin();
191     iter = mMapSinkInterface.find(sinkID);
192     if (iter != mMapSinkInterface.end())
193     {
194         handleData.connectionID = connectionID;
195         handle = createHandle(handleData, H_CONNECT);
196         mMapConnectionInterface.insert(std::make_pair(connectionID, iter->second));
197         mMapHandleInterface.insert(std::make_pair(handle.handle, iter->second));
198         return (iter->second->asyncConnect(handle, connectionID, sourceID, sinkID, connectionFormat));
199     }
200
201     return (E_NON_EXISTENT);
202 }
203
204 am_Error_e CAmRoutingSender::asyncDisconnect(am_Handle_s& handle, const am_connectionID_t connectionID)
205 {
206     am_handleData_c handleData;
207     ConnectionInterfaceMap::iterator iter = mMapConnectionInterface.begin();
208     iter = mMapConnectionInterface.find(connectionID);
209     if (iter != mMapConnectionInterface.end())
210     {
211         handleData.connectionID = connectionID;
212         handle = createHandle(handleData, H_DISCONNECT);
213         mMapHandleInterface.insert(std::make_pair(handle.handle, iter->second));
214         am_Error_e returnVal = iter->second->asyncDisconnect(handle, connectionID);
215         mMapConnectionInterface.erase(iter);
216         return (returnVal);
217     }
218
219     return (E_NON_EXISTENT);
220 }
221
222 am_Error_e CAmRoutingSender::asyncSetSinkVolume(am_Handle_s& handle, const am_sinkID_t sinkID, const am_volume_t volume, const am_RampType_e ramp, const am_time_t time)
223 {
224     am_handleData_c handleData;
225     SinkInterfaceMap::iterator iter = mMapSinkInterface.begin();
226     iter = mMapSinkInterface.find(sinkID);
227     if (iter != mMapSinkInterface.end())
228     {
229         handleData.sinkID = sinkID;
230         handleData.volume = volume;
231         handle = createHandle(handleData, H_SETSINKVOLUME);
232         mMapHandleInterface.insert(std::make_pair(handle.handle, iter->second));
233         return (iter->second->asyncSetSinkVolume(handle, sinkID, volume, ramp, time));
234     }
235     return (E_NON_EXISTENT);
236 }
237
238 am_Error_e CAmRoutingSender::asyncSetSourceVolume(am_Handle_s& handle, const am_sourceID_t sourceID, const am_volume_t volume, const am_RampType_e ramp, const am_time_t time)
239 {
240     am_handleData_c handleData;
241     SourceInterfaceMap::iterator iter = mMapSourceInterface.begin();
242     iter = mMapSourceInterface.find(sourceID);
243     if (iter != mMapSourceInterface.end())
244     {
245         handleData.sourceID = sourceID;
246         handleData.volume = volume;
247         handle = createHandle(handleData, H_SETSOURCEVOLUME);
248         mMapHandleInterface.insert(std::make_pair(handle.handle, iter->second));
249         return (iter->second->asyncSetSourceVolume(handle, sourceID, volume, ramp, time));
250     }
251     return (E_NON_EXISTENT);
252 }
253
254 am_Error_e CAmRoutingSender::asyncSetSourceState(am_Handle_s& handle, const am_sourceID_t sourceID, const am_SourceState_e state)
255 {
256     am_handleData_c handleData;
257     SourceInterfaceMap::iterator iter = mMapSourceInterface.begin();
258     iter = mMapSourceInterface.find(sourceID);
259     if (iter != mMapSourceInterface.end())
260     {
261         handleData.sourceID = sourceID;
262         handleData.sourceState = state;
263         handle = createHandle(handleData, H_SETSOURCESTATE);
264         mMapHandleInterface.insert(std::make_pair(handle.handle, iter->second));
265         return (iter->second->asyncSetSourceState(handle, sourceID, state));
266     }
267     return (E_NON_EXISTENT);
268 }
269
270 am_Error_e CAmRoutingSender::asyncSetSinkSoundProperty(am_Handle_s& handle, const am_sinkID_t sinkID, const am_SoundProperty_s & soundProperty)
271 {
272     am_handleData_c handleData;
273     SinkInterfaceMap::iterator iter = mMapSinkInterface.begin();
274     iter = mMapSinkInterface.find(sinkID);
275     if (iter != mMapSinkInterface.end())
276     {
277         handleData.sinkID = sinkID;
278         handleData.soundPropery = soundProperty;
279         handle = createHandle(handleData, H_SETSINKSOUNDPROPERTY);
280         mMapHandleInterface.insert(std::make_pair(handle.handle, iter->second));
281         return (iter->second->asyncSetSinkSoundProperty(handle, sinkID, soundProperty));
282     }
283     return (E_NON_EXISTENT);
284 }
285
286 am_Error_e CAmRoutingSender::asyncSetSourceSoundProperty(am_Handle_s& handle, const am_sourceID_t sourceID, const am_SoundProperty_s & soundProperty)
287 {
288     am_handleData_c handleData;
289     SourceInterfaceMap::iterator iter = mMapSourceInterface.begin();
290     iter = mMapSourceInterface.find(sourceID);
291     if (iter != mMapSourceInterface.end())
292     {
293         handleData.sourceID = sourceID;
294         handleData.soundPropery = soundProperty;
295         handle = createHandle(handleData, H_SETSOURCESOUNDPROPERTY);
296         mMapHandleInterface.insert(std::make_pair(handle.handle, iter->second));
297         return (iter->second->asyncSetSourceSoundProperty(handle, sourceID, soundProperty));
298     }
299     return (E_NON_EXISTENT);
300 }
301
302 am_Error_e CAmRoutingSender::asyncSetSourceSoundProperties(am_Handle_s& handle, const std::vector<am_SoundProperty_s> & listSoundProperties, const am_sourceID_t sourceID)
303 {
304     am_handleData_c handleData;
305     SourceInterfaceMap::iterator iter = mMapSourceInterface.begin();
306     iter = mMapSourceInterface.find(sourceID);
307     if (iter != mMapSourceInterface.end())
308     {
309         handleData.sourceID = sourceID;
310         handleData.soundProperties = new std::vector<am_SoundProperty_s>(listSoundProperties);
311         handle = createHandle(handleData, H_SETSOURCESOUNDPROPERTIES);
312         mMapHandleInterface.insert(std::make_pair(handle.handle, iter->second));
313         return (iter->second->asyncSetSourceSoundProperties(handle, sourceID, listSoundProperties));
314     }
315     return (E_NON_EXISTENT);
316 }
317
318 am_Error_e CAmRoutingSender::asyncSetSinkSoundProperties(am_Handle_s& handle, const std::vector<am_SoundProperty_s> & listSoundProperties, const am_sinkID_t sinkID)
319 {
320     am_handleData_c handleData;
321     SinkInterfaceMap::iterator iter = mMapSinkInterface.begin();
322     iter = mMapSinkInterface.find(sinkID);
323     if (iter != mMapSinkInterface.end())
324     {
325         handleData.sinkID = sinkID;
326         handleData.soundProperties = new std::vector<am_SoundProperty_s>(listSoundProperties);
327         handle = createHandle(handleData, H_SETSINKSOUNDPROPERTIES);
328         mMapHandleInterface.insert(std::make_pair(handle.handle, iter->second));
329         return (iter->second->asyncSetSinkSoundProperties(handle, sinkID, listSoundProperties));
330     }
331     return (E_NON_EXISTENT);
332
333 }
334
335 am_Error_e CAmRoutingSender::asyncCrossFade(am_Handle_s& handle, const am_crossfaderID_t crossfaderID, const am_HotSink_e hotSink, const am_RampType_e rampType, const am_time_t time)
336 {
337     am_handleData_c handleData;
338     CrossfaderInterfaceMap::iterator iter = mMapCrossfaderInterface.begin();
339     iter = mMapCrossfaderInterface.find(crossfaderID);
340     if (iter != mMapCrossfaderInterface.end())
341     {
342         handleData.crossfaderID = crossfaderID;
343         handleData.hotSink = hotSink;
344         handle = createHandle(handleData, H_CROSSFADE);
345         mMapHandleInterface.insert(std::make_pair(handle.handle, iter->second));
346         return (iter->second->asyncCrossFade(handle, crossfaderID, hotSink, rampType, time));
347     }
348     return (E_NON_EXISTENT);
349 }
350
351 am_Error_e CAmRoutingSender::setDomainState(const am_domainID_t domainID, const am_DomainState_e domainState)
352 {
353     DomainInterfaceMap::iterator iter = mMapDomainInterface.begin();
354     iter = mMapDomainInterface.find(domainID);
355     if (iter != mMapDomainInterface.end())
356         return (iter->second->setDomainState(domainID, domainState));
357     return (E_NON_EXISTENT);
358 }
359
360 /**
361  * @author Christian
362  * this adds the domain to the lookup table of the Router. The data is used to have a quick lookup of the correct pluginInterface.
363  * This must be done whenever a domain is registered.
364  */
365 am_Error_e CAmRoutingSender::addDomainLookup(const am_Domain_s& domainData)
366 {
367     std::vector<InterfaceNamePairs>::iterator iter = mListInterfaces.begin();
368     std::vector<InterfaceNamePairs>::iterator iterEnd = mListInterfaces.end();
369     for (; iter < iterEnd; ++iter)
370     {
371         if ((*iter).busName.compare(domainData.busname) == 0)
372         {
373             mMapDomainInterface.insert(std::make_pair(domainData.domainID, (*iter).routingInterface));
374             return (E_OK);
375         }
376     }
377
378     return (E_UNKNOWN);
379 }
380
381 /**
382  * @author Christian
383  * this adds the Source to the lookup table of the Router. The data is used to have a quick lookup of the correct pluginInterface.
384  * This must be done whenever a Source is registered.
385  */
386 am_Error_e CAmRoutingSender::addSourceLookup(const am_Source_s& sourceData)
387 {
388     DomainInterfaceMap::iterator iter = mMapDomainInterface.begin();
389     iter = mMapDomainInterface.find(sourceData.domainID);
390     if (iter != mMapDomainInterface.end())
391     {
392         mMapSourceInterface.insert(std::make_pair(sourceData.sourceID, iter->second));
393         return (E_OK);
394     }
395
396     return (E_UNKNOWN);
397 }
398
399 /**
400  * @author Christian
401  * this adds the Sink to the lookup table of the Router. The data is used to have a quick lookup of the correct pluginInterface.
402  * This must be done whenever a Sink is registered.
403  */
404 am_Error_e CAmRoutingSender::addSinkLookup(const am_Sink_s& sinkData)
405 {
406     DomainInterfaceMap::iterator iter = mMapDomainInterface.begin();
407     iter = mMapDomainInterface.find(sinkData.domainID);
408     if (iter != mMapDomainInterface.end())
409     {
410         mMapSinkInterface.insert(std::make_pair(sinkData.sinkID, iter->second));
411         return (E_OK);
412     }
413
414     return (E_UNKNOWN);
415 }
416
417 /**
418  * @author Christian
419  * this adds the Crossfader to the lookup table of the Router. The data is used to have a quick lookup of the correct pluginInterface.
420  * This must be done whenever a Crossfader is registered.
421  */
422 am_Error_e CAmRoutingSender::addCrossfaderLookup(const am_Crossfader_s& crossfaderData)
423 {
424     DomainInterfaceMap::iterator iter = mMapSourceInterface.begin();
425     iter = mMapSourceInterface.find(crossfaderData.sourceID);
426     if (iter != mMapSourceInterface.end())
427     {
428         mMapSourceInterface.insert(std::make_pair(crossfaderData.crossfaderID, iter->second));
429         return (E_OK);
430     }
431
432     return (E_UNKNOWN);
433 }
434
435 /**
436  * @author Christian
437  * this removes the Domain to the lookup table of the Router. This must be done everytime a domain is deregistered.
438  */
439 am_Error_e CAmRoutingSender::removeDomainLookup(const am_domainID_t domainID)
440 {
441     DomainInterfaceMap::iterator iter = mMapDomainInterface.begin();
442     iter = mMapDomainInterface.find(domainID);
443     if (iter != mMapDomainInterface.end())
444     {
445         mMapDomainInterface.erase(iter);
446         return (E_OK);
447     }
448
449     return (E_NON_EXISTENT);
450 }
451
452 /**
453  * @author Christian
454  * this removes the Source to the lookup table of the Router. This must be done everytime a source is deregistered.
455  */
456 am_Error_e CAmRoutingSender::removeSourceLookup(const am_sourceID_t sourceID)
457 {
458     SourceInterfaceMap::iterator iter = mMapSourceInterface.begin();
459     iter = mMapSourceInterface.find(sourceID);
460     if (iter != mMapSourceInterface.end())
461     {
462         mMapSourceInterface.erase(iter);
463         return (E_OK);
464     }
465
466     return (E_NON_EXISTENT);
467 }
468
469 /**
470  * @author Christian
471  * this removes the Sink to the lookup table of the Router. This must be done everytime a sink is deregistered.
472  */
473 am_Error_e CAmRoutingSender::removeSinkLookup(const am_sinkID_t sinkID)
474 {
475     SinkInterfaceMap::iterator iter = mMapSinkInterface.begin();
476     iter = mMapSinkInterface.find(sinkID);
477     if (iter != mMapSinkInterface.end())
478     {
479         mMapSinkInterface.erase(iter);
480         return (E_OK);
481     }
482
483     return (E_NON_EXISTENT);
484 }
485
486 /**
487  * @author Christian
488  * this removes the Crossfader to the lookup table of the Router. This must be done everytime a crossfader is deregistered.
489  */
490 am_Error_e CAmRoutingSender::removeCrossfaderLookup(const am_crossfaderID_t crossfaderID)
491 {
492     CrossfaderInterfaceMap::iterator iter = mMapCrossfaderInterface.begin();
493     iter = mMapCrossfaderInterface.find(crossfaderID);
494     if (iter != mMapCrossfaderInterface.end())
495     {
496         mMapCrossfaderInterface.erase(iter);
497         return (E_OK);
498     }
499
500     return (E_NON_EXISTENT);
501 }
502
503 /**
504  * removes a handle from the list
505  * @param handle to be removed
506  * @return E_OK in case of success
507  */
508 am_Error_e CAmRoutingSender::removeHandle(const am_Handle_s& handle)
509 {
510     if (mlistActiveHandles.erase(handle))
511         return (E_OK);
512     return (E_UNKNOWN);
513 }
514
515 am_Error_e CAmRoutingSender::getListHandles(std::vector<am_Handle_s> & listHandles) const
516 {
517     listHandles.clear();
518     HandlesMap::const_iterator it = mlistActiveHandles.begin();
519     for (; it != mlistActiveHandles.end(); ++it)
520     {
521         listHandles.push_back(it->first);
522     }
523     return (E_OK);
524 }
525
526 /**
527  * creates a handle and adds it to the list of handles
528  * @param handleData the data that should be saves together with the handle
529  * @param type the type of handle to be created
530  * @return the handle
531  */
532 am_Handle_s CAmRoutingSender::createHandle(const am_handleData_c& handleData, const am_Handle_e type)
533 {
534     am_Handle_s handle;
535     handle.handle = ++mHandleCount; //todo: handle overflows here...
536     handle.handleType = type;
537     mlistActiveHandles.insert(std::make_pair(handle, handleData));
538     return (handle);
539 }
540
541 /**
542  * returns the data that belong to handles
543  * @param handle the handle
544  * @return a class holding the handle data
545  */
546 CAmRoutingSender::am_handleData_c CAmRoutingSender::returnHandleData(const am_Handle_s handle) const
547 {
548     HandlesMap::const_iterator it = mlistActiveHandles.begin();
549     it = mlistActiveHandles.find(handle);
550     return (it->second);
551 }
552
553 void CAmRoutingSender::setRoutingReady()
554 {
555     mpRoutingReceiver->waitOnStartup(false);
556     std::vector<InterfaceNamePairs>::iterator iter = mListInterfaces.begin();
557     std::vector<InterfaceNamePairs>::iterator iterEnd = mListInterfaces.end();
558     for (; iter < iterEnd; ++iter)
559     {
560         (*iter).routingInterface->setRoutingReady(mpRoutingReceiver->getStartupHandle());
561     }
562     mpRoutingReceiver->waitOnStartup(true);
563 }
564
565 void CAmRoutingSender::setRoutingRundown()
566 {
567     mpRoutingReceiver->waitOnRundown(false);
568     std::vector<InterfaceNamePairs>::iterator iter = mListInterfaces.begin();
569     std::vector<InterfaceNamePairs>::iterator iterEnd = mListInterfaces.end();
570     for (; iter < iterEnd; ++iter)
571     {
572         (*iter).routingInterface->setRoutingRundown(mpRoutingReceiver->getStartupHandle());
573     }
574     mpRoutingReceiver->waitOnRundown(true);
575 }
576
577 void CAmRoutingSender::unloadLibraries(void)
578 {
579     std::vector<void*>::iterator iterator = mListLibraryHandles.begin();
580     for (; iterator < mListLibraryHandles.end(); ++iterator)
581     {
582         dlclose(*iterator);
583     }
584     mListLibraryHandles.clear();
585 }
586
587 am_Error_e CAmRoutingSender::getListPlugins(std::vector<std::string>& interfaces) const
588 {
589     std::vector<InterfaceNamePairs>::const_iterator it = mListInterfaces.begin();
590     for (; it != mListInterfaces.end(); ++it)
591     {
592         interfaces.push_back(it->busName);
593     }
594     return (E_OK);
595 }
596
597 void CAmRoutingSender::getInterfaceVersion(std::string & version) const
598 {
599     version = RoutingSendVersion;
600 }
601 }