1
0

VivoxVoiceModule.cs 59 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328
  1. /*
  2. * Copyright (c) Contributors, http://opensimulator.org/
  3. * See CONTRIBUTORS.TXT for a full list of copyright holders.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. * * Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * * Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. * * Neither the name of the OpenSim Project nor the
  13. * names of its contributors may be used to endorse or promote products
  14. * derived from this software without specific prior written permission.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
  17. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
  20. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. using System;
  28. using System.IO;
  29. using System.Net;
  30. using System.Text;
  31. using System.Xml;
  32. using System.Collections;
  33. using System.Collections.Generic;
  34. using System.Reflection;
  35. using System.Threading;
  36. using OpenMetaverse;
  37. using log4net;
  38. using Mono.Addins;
  39. using Nini.Config;
  40. using Nwc.XmlRpc;
  41. using OpenSim.Framework;
  42. using OpenSim.Framework.Capabilities;
  43. using OpenSim.Framework.Servers;
  44. using OpenSim.Framework.Servers.HttpServer;
  45. using OpenSim.Region.Framework.Interfaces;
  46. using OpenSim.Region.Framework.Scenes;
  47. using Caps = OpenSim.Framework.Capabilities.Caps;
  48. namespace OpenSim.Region.OptionalModules.Avatar.Voice.VivoxVoice
  49. {
  50. [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "VivoxVoiceModule")]
  51. public class VivoxVoiceModule : ISharedRegionModule
  52. {
  53. // channel distance model values
  54. public const int CHAN_DIST_NONE = 0; // no attenuation
  55. public const int CHAN_DIST_INVERSE = 1; // inverse distance attenuation
  56. public const int CHAN_DIST_LINEAR = 2; // linear attenuation
  57. public const int CHAN_DIST_EXPONENT = 3; // exponential attenuation
  58. public const int CHAN_DIST_DEFAULT = CHAN_DIST_LINEAR;
  59. // channel type values
  60. public static readonly string CHAN_TYPE_POSITIONAL = "positional";
  61. public static readonly string CHAN_TYPE_CHANNEL = "channel";
  62. public static readonly string CHAN_TYPE_DEFAULT = CHAN_TYPE_POSITIONAL;
  63. // channel mode values
  64. public static readonly string CHAN_MODE_OPEN = "open";
  65. public static readonly string CHAN_MODE_LECTURE = "lecture";
  66. public static readonly string CHAN_MODE_PRESENTATION = "presentation";
  67. public static readonly string CHAN_MODE_AUDITORIUM = "auditorium";
  68. public static readonly string CHAN_MODE_DEFAULT = CHAN_MODE_OPEN;
  69. // unconstrained default values
  70. public const double CHAN_ROLL_OFF_DEFAULT = 2.0; // rate of attenuation
  71. public const double CHAN_ROLL_OFF_MIN = 1.0;
  72. public const double CHAN_ROLL_OFF_MAX = 4.0;
  73. public const int CHAN_MAX_RANGE_DEFAULT = 80; // distance at which channel is silent
  74. public const int CHAN_MAX_RANGE_MIN = 0;
  75. public const int CHAN_MAX_RANGE_MAX = 160;
  76. public const int CHAN_CLAMPING_DISTANCE_DEFAULT = 10; // distance before attenuation applies
  77. public const int CHAN_CLAMPING_DISTANCE_MIN = 0;
  78. public const int CHAN_CLAMPING_DISTANCE_MAX = 160;
  79. // Infrastructure
  80. private static readonly ILog m_log =
  81. LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  82. private static readonly Object vlock = new Object();
  83. // Capability strings
  84. private static readonly string m_parcelVoiceInfoRequestPath = "0107/";
  85. private static readonly string m_provisionVoiceAccountRequestPath = "0108/";
  86. private static readonly string m_chatSessionRequestPath = "0109/";
  87. // Control info, e.g. vivox server, admin user, admin password
  88. private static bool m_pluginEnabled = false;
  89. private static bool m_adminConnected = false;
  90. private static string m_vivoxServer;
  91. private static string m_vivoxSipUri;
  92. private static string m_vivoxVoiceAccountApi;
  93. private static string m_vivoxAdminUser;
  94. private static string m_vivoxAdminPassword;
  95. private static string m_authToken = String.Empty;
  96. private static int m_vivoxChannelDistanceModel;
  97. private static double m_vivoxChannelRollOff;
  98. private static int m_vivoxChannelMaximumRange;
  99. private static string m_vivoxChannelMode;
  100. private static string m_vivoxChannelType;
  101. private static int m_vivoxChannelClampingDistance;
  102. private static Dictionary<string,string> m_parents = new Dictionary<string,string>();
  103. private static bool m_dumpXml;
  104. private IConfig m_config;
  105. private object m_Lock;
  106. public void Initialise(IConfigSource config)
  107. {
  108. m_config = config.Configs["VivoxVoice"];
  109. if (null == m_config)
  110. return;
  111. if (!m_config.GetBoolean("enabled", false))
  112. return;
  113. m_Lock = new object();
  114. try
  115. {
  116. // retrieve configuration variables
  117. m_vivoxServer = m_config.GetString("vivox_server", String.Empty);
  118. m_vivoxSipUri = m_config.GetString("vivox_sip_uri", String.Empty);
  119. m_vivoxAdminUser = m_config.GetString("vivox_admin_user", String.Empty);
  120. m_vivoxAdminPassword = m_config.GetString("vivox_admin_password", String.Empty);
  121. m_vivoxChannelDistanceModel = m_config.GetInt("vivox_channel_distance_model", CHAN_DIST_DEFAULT);
  122. m_vivoxChannelRollOff = m_config.GetDouble("vivox_channel_roll_off", CHAN_ROLL_OFF_DEFAULT);
  123. m_vivoxChannelMaximumRange = m_config.GetInt("vivox_channel_max_range", CHAN_MAX_RANGE_DEFAULT);
  124. m_vivoxChannelMode = m_config.GetString("vivox_channel_mode", CHAN_MODE_DEFAULT).ToLower();
  125. m_vivoxChannelType = m_config.GetString("vivox_channel_type", CHAN_TYPE_DEFAULT).ToLower();
  126. m_vivoxChannelClampingDistance = m_config.GetInt("vivox_channel_clamping_distance",
  127. CHAN_CLAMPING_DISTANCE_DEFAULT);
  128. m_dumpXml = m_config.GetBoolean("dump_xml", false);
  129. // Validate against constraints and default if necessary
  130. if (m_vivoxChannelRollOff < CHAN_ROLL_OFF_MIN || m_vivoxChannelRollOff > CHAN_ROLL_OFF_MAX)
  131. {
  132. m_log.WarnFormat("[VivoxVoice] Invalid value for roll off ({0}), reset to {1}.",
  133. m_vivoxChannelRollOff, CHAN_ROLL_OFF_DEFAULT);
  134. m_vivoxChannelRollOff = CHAN_ROLL_OFF_DEFAULT;
  135. }
  136. if (m_vivoxChannelMaximumRange < CHAN_MAX_RANGE_MIN || m_vivoxChannelMaximumRange > CHAN_MAX_RANGE_MAX)
  137. {
  138. m_log.WarnFormat("[VivoxVoice] Invalid value for maximum range ({0}), reset to {1}.",
  139. m_vivoxChannelMaximumRange, CHAN_MAX_RANGE_DEFAULT);
  140. m_vivoxChannelMaximumRange = CHAN_MAX_RANGE_DEFAULT;
  141. }
  142. if (m_vivoxChannelClampingDistance < CHAN_CLAMPING_DISTANCE_MIN ||
  143. m_vivoxChannelClampingDistance > CHAN_CLAMPING_DISTANCE_MAX)
  144. {
  145. m_log.WarnFormat("[VivoxVoice] Invalid value for clamping distance ({0}), reset to {1}.",
  146. m_vivoxChannelClampingDistance, CHAN_CLAMPING_DISTANCE_DEFAULT);
  147. m_vivoxChannelClampingDistance = CHAN_CLAMPING_DISTANCE_DEFAULT;
  148. }
  149. switch (m_vivoxChannelMode)
  150. {
  151. case "open" : break;
  152. case "lecture" : break;
  153. case "presentation" : break;
  154. case "auditorium" : break;
  155. default :
  156. m_log.WarnFormat("[VivoxVoice] Invalid value for channel mode ({0}), reset to {1}.",
  157. m_vivoxChannelMode, CHAN_MODE_DEFAULT);
  158. m_vivoxChannelMode = CHAN_MODE_DEFAULT;
  159. break;
  160. }
  161. switch (m_vivoxChannelType)
  162. {
  163. case "positional" : break;
  164. case "channel" : break;
  165. default :
  166. m_log.WarnFormat("[VivoxVoice] Invalid value for channel type ({0}), reset to {1}.",
  167. m_vivoxChannelType, CHAN_TYPE_DEFAULT);
  168. m_vivoxChannelType = CHAN_TYPE_DEFAULT;
  169. break;
  170. }
  171. m_vivoxVoiceAccountApi = String.Format("http://{0}/api2", m_vivoxServer);
  172. // Admin interface required values
  173. if (String.IsNullOrEmpty(m_vivoxServer) ||
  174. String.IsNullOrEmpty(m_vivoxSipUri) ||
  175. String.IsNullOrEmpty(m_vivoxAdminUser) ||
  176. String.IsNullOrEmpty(m_vivoxAdminPassword))
  177. {
  178. m_log.Error("[VivoxVoice] plugin mis-configured");
  179. m_log.Info("[VivoxVoice] plugin disabled: incomplete configuration");
  180. return;
  181. }
  182. m_log.InfoFormat("[VivoxVoice] using vivox server {0}", m_vivoxServer);
  183. // Get admin rights and cleanup any residual channel definition
  184. DoAdminLogin();
  185. m_pluginEnabled = true;
  186. m_log.Info("[VivoxVoice] plugin enabled");
  187. }
  188. catch (Exception e)
  189. {
  190. m_log.ErrorFormat("[VivoxVoice] plugin initialization failed: {0}", e.Message);
  191. m_log.DebugFormat("[VivoxVoice] plugin initialization failed: {0}", e.ToString());
  192. return;
  193. }
  194. }
  195. public void AddRegion(Scene scene)
  196. {
  197. if (m_pluginEnabled)
  198. {
  199. lock (vlock)
  200. {
  201. string channelId;
  202. string sceneUUID = scene.RegionInfo.RegionID.ToString();
  203. string sceneName = scene.RegionInfo.RegionName;
  204. // Make sure that all local channels are deleted.
  205. // So we have to search for the children, and then do an
  206. // iteration over the set of chidren identified.
  207. // This assumes that there is just one directory per
  208. // region.
  209. if (VivoxTryGetDirectory(sceneUUID + "D", out channelId))
  210. {
  211. m_log.DebugFormat("[VivoxVoice]: region {0}: uuid {1}: located directory id {2}",
  212. sceneName, sceneUUID, channelId);
  213. XmlElement children = VivoxListChildren(channelId);
  214. string count;
  215. if (XmlFind(children, "response.level0.channel-search.count", out count))
  216. {
  217. int cnum = Convert.ToInt32(count);
  218. for (int i = 0; i < cnum; i++)
  219. {
  220. string id;
  221. if (XmlFind(children, "response.level0.channel-search.channels.channels.level4.id", i, out id))
  222. {
  223. if (!IsOK(VivoxDeleteChannel(channelId, id)))
  224. m_log.WarnFormat("[VivoxVoice] Channel delete failed {0}:{1}:{2}", i, channelId, id);
  225. }
  226. }
  227. }
  228. }
  229. else
  230. {
  231. if (!VivoxTryCreateDirectory(sceneUUID + "D", sceneName, out channelId))
  232. {
  233. m_log.WarnFormat("[VivoxVoice] Create failed <{0}:{1}:{2}>",
  234. "*", sceneUUID, sceneName);
  235. channelId = String.Empty;
  236. }
  237. }
  238. // Create a dictionary entry unconditionally. This eliminates the
  239. // need to check for a parent in the core code. The end result is
  240. // the same, if the parent table entry is an empty string, then
  241. // region channels will be created as first-level channels.
  242. lock (m_parents)
  243. {
  244. if (m_parents.ContainsKey(sceneUUID))
  245. {
  246. RemoveRegion(scene);
  247. m_parents.Add(sceneUUID, channelId);
  248. }
  249. else
  250. {
  251. m_parents.Add(sceneUUID, channelId);
  252. }
  253. }
  254. }
  255. // we need to capture scene in an anonymous method
  256. // here as we need it later in the callbacks
  257. scene.EventManager.OnRegisterCaps += delegate(UUID agentID, Caps caps)
  258. {
  259. OnRegisterCaps(scene, agentID, caps);
  260. };
  261. }
  262. }
  263. public void RegionLoaded(Scene scene)
  264. {
  265. // Do nothing.
  266. }
  267. public void RemoveRegion(Scene scene)
  268. {
  269. if (m_pluginEnabled)
  270. {
  271. lock (vlock)
  272. {
  273. string channelId;
  274. string sceneUUID = scene.RegionInfo.RegionID.ToString();
  275. string sceneName = scene.RegionInfo.RegionName;
  276. // Make sure that all local channels are deleted.
  277. // So we have to search for the children, and then do an
  278. // iteration over the set of chidren identified.
  279. // This assumes that there is just one directory per
  280. // region.
  281. if (VivoxTryGetDirectory(sceneUUID + "D", out channelId))
  282. {
  283. m_log.DebugFormat("[VivoxVoice]: region {0}: uuid {1}: located directory id {2}",
  284. sceneName, sceneUUID, channelId);
  285. XmlElement children = VivoxListChildren(channelId);
  286. string count;
  287. if (XmlFind(children, "response.level0.channel-search.count", out count))
  288. {
  289. int cnum = Convert.ToInt32(count);
  290. for (int i = 0; i < cnum; i++)
  291. {
  292. string id;
  293. if (XmlFind(children, "response.level0.channel-search.channels.channels.level4.id", i, out id))
  294. {
  295. if (!IsOK(VivoxDeleteChannel(channelId, id)))
  296. m_log.WarnFormat("[VivoxVoice] Channel delete failed {0}:{1}:{2}", i, channelId, id);
  297. }
  298. }
  299. }
  300. }
  301. if (!IsOK(VivoxDeleteChannel(null, channelId)))
  302. m_log.WarnFormat("[VivoxVoice] Parent channel delete failed {0}:{1}:{2}", sceneName, sceneUUID, channelId);
  303. // Remove the channel umbrella entry
  304. lock (m_parents)
  305. {
  306. if (m_parents.ContainsKey(sceneUUID))
  307. {
  308. m_parents.Remove(sceneUUID);
  309. }
  310. }
  311. }
  312. }
  313. }
  314. public void PostInitialise()
  315. {
  316. // Do nothing.
  317. }
  318. public void Close()
  319. {
  320. if (m_pluginEnabled)
  321. VivoxLogout();
  322. }
  323. public Type ReplaceableInterface
  324. {
  325. get { return null; }
  326. }
  327. public string Name
  328. {
  329. get { return "VivoxVoiceModule"; }
  330. }
  331. public bool IsSharedModule
  332. {
  333. get { return true; }
  334. }
  335. // <summary>
  336. // OnRegisterCaps is invoked via the scene.EventManager
  337. // everytime OpenSim hands out capabilities to a client
  338. // (login, region crossing). We contribute two capabilities to
  339. // the set of capabilities handed back to the client:
  340. // ProvisionVoiceAccountRequest and ParcelVoiceInfoRequest.
  341. //
  342. // ProvisionVoiceAccountRequest allows the client to obtain
  343. // the voice account credentials for the avatar it is
  344. // controlling (e.g., user name, password, etc).
  345. //
  346. // ParcelVoiceInfoRequest is invoked whenever the client
  347. // changes from one region or parcel to another.
  348. //
  349. // Note that OnRegisterCaps is called here via a closure
  350. // delegate containing the scene of the respective region (see
  351. // Initialise()).
  352. // </summary>
  353. public void OnRegisterCaps(Scene scene, UUID agentID, Caps caps)
  354. {
  355. m_log.DebugFormat("[VivoxVoice] OnRegisterCaps: agentID {0} caps {1}", agentID, caps);
  356. string capsBase = "/CAPS/" + caps.CapsObjectPath;
  357. caps.RegisterHandler(
  358. "ProvisionVoiceAccountRequest",
  359. new RestStreamHandler(
  360. "POST",
  361. capsBase + m_provisionVoiceAccountRequestPath,
  362. (request, path, param, httpRequest, httpResponse)
  363. => ProvisionVoiceAccountRequest(scene, request, path, param, agentID, caps),
  364. "ProvisionVoiceAccountRequest",
  365. agentID.ToString()));
  366. caps.RegisterHandler(
  367. "ParcelVoiceInfoRequest",
  368. new RestStreamHandler(
  369. "POST",
  370. capsBase + m_parcelVoiceInfoRequestPath,
  371. (request, path, param, httpRequest, httpResponse)
  372. => ParcelVoiceInfoRequest(scene, request, path, param, agentID, caps),
  373. "ParcelVoiceInfoRequest",
  374. agentID.ToString()));
  375. //caps.RegisterHandler(
  376. // "ChatSessionRequest",
  377. // new RestStreamHandler(
  378. // "POST",
  379. // capsBase + m_chatSessionRequestPath,
  380. // (request, path, param, httpRequest, httpResponse)
  381. // => ChatSessionRequest(scene, request, path, param, agentID, caps),
  382. // "ChatSessionRequest",
  383. // agentID.ToString()));
  384. }
  385. /// <summary>
  386. /// Callback for a client request for Voice Account Details
  387. /// </summary>
  388. /// <param name="scene">current scene object of the client</param>
  389. /// <param name="request"></param>
  390. /// <param name="path"></param>
  391. /// <param name="param"></param>
  392. /// <param name="agentID"></param>
  393. /// <param name="caps"></param>
  394. /// <returns></returns>
  395. public string ProvisionVoiceAccountRequest(Scene scene, string request, string path, string param,
  396. UUID agentID, Caps caps)
  397. {
  398. try
  399. {
  400. ScenePresence avatar = null;
  401. string avatarName = null;
  402. if (scene == null)
  403. throw new Exception("[VivoxVoice][PROVISIONVOICE]: Invalid scene");
  404. avatar = scene.GetScenePresence(agentID);
  405. while (avatar == null)
  406. {
  407. Thread.Sleep(100);
  408. avatar = scene.GetScenePresence(agentID);
  409. }
  410. avatarName = avatar.Name;
  411. m_log.DebugFormat("[VivoxVoice][PROVISIONVOICE]: scene = {0}, agentID = {1}", scene, agentID);
  412. m_log.DebugFormat("[VivoxVoice][PROVISIONVOICE]: request: {0}, path: {1}, param: {2}",
  413. request, path, param);
  414. XmlElement resp;
  415. bool retry = false;
  416. string agentname = "x" + Convert.ToBase64String(agentID.GetBytes());
  417. string password = new UUID(Guid.NewGuid()).ToString().Replace('-','Z').Substring(0,16);
  418. string code = String.Empty;
  419. agentname = agentname.Replace('+', '-').Replace('/', '_');
  420. do
  421. {
  422. resp = VivoxGetAccountInfo(agentname);
  423. if (XmlFind(resp, "response.level0.status", out code))
  424. {
  425. if (code != "OK")
  426. {
  427. if (XmlFind(resp, "response.level0.body.code", out code))
  428. {
  429. // If the request was recognized, then this should be set to something
  430. switch (code)
  431. {
  432. case "201" : // Account expired
  433. m_log.ErrorFormat("[VivoxVoice]: avatar \"{0}\": Get account information failed : expired credentials",
  434. avatarName);
  435. m_adminConnected = false;
  436. retry = DoAdminLogin();
  437. break;
  438. case "202" : // Missing credentials
  439. m_log.ErrorFormat("[VivoxVoice]: avatar \"{0}\": Get account information failed : missing credentials",
  440. avatarName);
  441. break;
  442. case "212" : // Not authorized
  443. m_log.ErrorFormat("[VivoxVoice]: avatar \"{0}\": Get account information failed : not authorized",
  444. avatarName);
  445. break;
  446. case "300" : // Required parameter missing
  447. m_log.ErrorFormat("[VivoxVoice]: avatar \"{0}\": Get account information failed : parameter missing",
  448. avatarName);
  449. break;
  450. case "403" : // Account does not exist
  451. resp = VivoxCreateAccount(agentname,password);
  452. // Note: This REALLY MUST BE status. Create Account does not return code.
  453. if (XmlFind(resp, "response.level0.status", out code))
  454. {
  455. switch (code)
  456. {
  457. case "201" : // Account expired
  458. m_log.ErrorFormat("[VivoxVoice]: avatar \"{0}\": Create account information failed : expired credentials",
  459. avatarName);
  460. m_adminConnected = false;
  461. retry = DoAdminLogin();
  462. break;
  463. case "202" : // Missing credentials
  464. m_log.ErrorFormat("[VivoxVoice]: avatar \"{0}\": Create account information failed : missing credentials",
  465. avatarName);
  466. break;
  467. case "212" : // Not authorized
  468. m_log.ErrorFormat("[VivoxVoice]: avatar \"{0}\": Create account information failed : not authorized",
  469. avatarName);
  470. break;
  471. case "300" : // Required parameter missing
  472. m_log.ErrorFormat("[VivoxVoice]: avatar \"{0}\": Create account information failed : parameter missing",
  473. avatarName);
  474. break;
  475. case "400" : // Create failed
  476. m_log.ErrorFormat("[VivoxVoice]: avatar \"{0}\": Create account information failed : create failed",
  477. avatarName);
  478. break;
  479. }
  480. }
  481. break;
  482. case "404" : // Failed to retrieve account
  483. m_log.ErrorFormat("[VivoxVoice]: avatar \"{0}\": Get account information failed : retrieve failed");
  484. // [AMW] Sleep and retry for a fixed period? Or just abandon?
  485. break;
  486. }
  487. }
  488. }
  489. }
  490. }
  491. while (retry);
  492. if (code != "OK")
  493. {
  494. m_log.DebugFormat("[VivoxVoice][PROVISIONVOICE]: Get Account Request failed for \"{0}\"", avatarName);
  495. throw new Exception("Unable to execute request");
  496. }
  497. // Unconditionally change the password on each request
  498. VivoxPassword(agentname, password);
  499. LLSDVoiceAccountResponse voiceAccountResponse =
  500. new LLSDVoiceAccountResponse(agentname, password, m_vivoxSipUri, m_vivoxVoiceAccountApi);
  501. string r = LLSDHelpers.SerialiseLLSDReply(voiceAccountResponse);
  502. m_log.DebugFormat("[VivoxVoice][PROVISIONVOICE]: avatar \"{0}\": {1}", avatarName, r);
  503. return r;
  504. }
  505. catch (Exception e)
  506. {
  507. m_log.ErrorFormat("[VivoxVoice][PROVISIONVOICE]: : {0}, retry later", e.Message);
  508. m_log.DebugFormat("[VivoxVoice][PROVISIONVOICE]: : {0} failed", e.ToString());
  509. return "<llsd><undef /></llsd>";
  510. }
  511. }
  512. /// <summary>
  513. /// Callback for a client request for ParcelVoiceInfo
  514. /// </summary>
  515. /// <param name="scene">current scene object of the client</param>
  516. /// <param name="request"></param>
  517. /// <param name="path"></param>
  518. /// <param name="param"></param>
  519. /// <param name="agentID"></param>
  520. /// <param name="caps"></param>
  521. /// <returns></returns>
  522. public string ParcelVoiceInfoRequest(Scene scene, string request, string path, string param,
  523. UUID agentID, Caps caps)
  524. {
  525. ScenePresence avatar = scene.GetScenePresence(agentID);
  526. string avatarName = avatar.Name;
  527. // - check whether we have a region channel in our cache
  528. // - if not:
  529. // create it and cache it
  530. // - send it to the client
  531. // - send channel_uri: as "sip:regionID@m_sipDomain"
  532. try
  533. {
  534. LLSDParcelVoiceInfoResponse parcelVoiceInfo;
  535. string channel_uri;
  536. if (null == scene.LandChannel)
  537. throw new Exception(String.Format("region \"{0}\": avatar \"{1}\": land data not yet available",
  538. scene.RegionInfo.RegionName, avatarName));
  539. // get channel_uri: check first whether estate
  540. // settings allow voice, then whether parcel allows
  541. // voice, if all do retrieve or obtain the parcel
  542. // voice channel
  543. LandData land = scene.GetLandData(avatar.AbsolutePosition);
  544. m_log.DebugFormat("[VivoxVoice][PARCELVOICE]: region \"{0}\": Parcel \"{1}\" ({2}): avatar \"{3}\": request: {4}, path: {5}, param: {6}",
  545. scene.RegionInfo.RegionName, land.Name, land.LocalID, avatarName, request, path, param);
  546. // m_log.DebugFormat("[VivoxVoice][PARCELVOICE]: avatar \"{0}\": location: {1} {2} {3}",
  547. // avatarName, avatar.AbsolutePosition.X, avatar.AbsolutePosition.Y, avatar.AbsolutePosition.Z);
  548. // TODO: EstateSettings don't seem to get propagated...
  549. if (!scene.RegionInfo.EstateSettings.AllowVoice)
  550. {
  551. m_log.DebugFormat("[VivoxVoice][PARCELVOICE]: region \"{0}\": voice not enabled in estate settings",
  552. scene.RegionInfo.RegionName);
  553. channel_uri = String.Empty;
  554. }
  555. if ((land.Flags & (uint)ParcelFlags.AllowVoiceChat) == 0)
  556. {
  557. m_log.DebugFormat("[VivoxVoice][PARCELVOICE]: region \"{0}\": Parcel \"{1}\" ({2}): avatar \"{3}\": voice not enabled for parcel",
  558. scene.RegionInfo.RegionName, land.Name, land.LocalID, avatarName);
  559. channel_uri = String.Empty;
  560. }
  561. else
  562. {
  563. channel_uri = RegionGetOrCreateChannel(scene, land);
  564. }
  565. // fill in our response to the client
  566. Hashtable creds = new Hashtable();
  567. creds["channel_uri"] = channel_uri;
  568. parcelVoiceInfo = new LLSDParcelVoiceInfoResponse(scene.RegionInfo.RegionName, land.LocalID, creds);
  569. string r = LLSDHelpers.SerialiseLLSDReply(parcelVoiceInfo);
  570. m_log.DebugFormat("[VivoxVoice][PARCELVOICE]: region \"{0}\": Parcel \"{1}\" ({2}): avatar \"{3}\": {4}",
  571. scene.RegionInfo.RegionName, land.Name, land.LocalID, avatarName, r);
  572. return r;
  573. }
  574. catch (Exception e)
  575. {
  576. m_log.ErrorFormat("[VivoxVoice][PARCELVOICE]: region \"{0}\": avatar \"{1}\": {2}, retry later",
  577. scene.RegionInfo.RegionName, avatarName, e.Message);
  578. m_log.DebugFormat("[VivoxVoice][PARCELVOICE]: region \"{0}\": avatar \"{1}\": {2} failed",
  579. scene.RegionInfo.RegionName, avatarName, e.ToString());
  580. return "<llsd><undef /></llsd>";
  581. }
  582. }
  583. /// <summary>
  584. /// Callback for a client request for a private chat channel
  585. /// </summary>
  586. /// <param name="scene">current scene object of the client</param>
  587. /// <param name="request"></param>
  588. /// <param name="path"></param>
  589. /// <param name="param"></param>
  590. /// <param name="agentID"></param>
  591. /// <param name="caps"></param>
  592. /// <returns></returns>
  593. public string ChatSessionRequest(Scene scene, string request, string path, string param,
  594. UUID agentID, Caps caps)
  595. {
  596. ScenePresence avatar = scene.GetScenePresence(agentID);
  597. string avatarName = avatar.Name;
  598. m_log.DebugFormat("[VivoxVoice][CHATSESSION]: avatar \"{0}\": request: {1}, path: {2}, param: {3}",
  599. avatarName, request, path, param);
  600. return "<llsd>true</llsd>";
  601. }
  602. private string RegionGetOrCreateChannel(Scene scene, LandData land)
  603. {
  604. string channelUri = null;
  605. string channelId = null;
  606. string landUUID;
  607. string landName;
  608. string parentId;
  609. lock (m_parents)
  610. parentId = m_parents[scene.RegionInfo.RegionID.ToString()];
  611. // Create parcel voice channel. If no parcel exists, then the voice channel ID is the same
  612. // as the directory ID. Otherwise, it reflects the parcel's ID.
  613. if (land.LocalID != 1 && (land.Flags & (uint)ParcelFlags.UseEstateVoiceChan) == 0)
  614. {
  615. landName = String.Format("{0}:{1}", scene.RegionInfo.RegionName, land.Name);
  616. landUUID = land.GlobalID.ToString();
  617. m_log.DebugFormat("[VivoxVoice]: Region:Parcel \"{0}\": parcel id {1}: using channel name {2}",
  618. landName, land.LocalID, landUUID);
  619. }
  620. else
  621. {
  622. landName = String.Format("{0}:{1}", scene.RegionInfo.RegionName, scene.RegionInfo.RegionName);
  623. landUUID = scene.RegionInfo.RegionID.ToString();
  624. m_log.DebugFormat("[VivoxVoice]: Region:Parcel \"{0}\": parcel id {1}: using channel name {2}",
  625. landName, land.LocalID, landUUID);
  626. }
  627. lock (vlock)
  628. {
  629. // Added by Adam to help debug channel not availible errors.
  630. if (VivoxTryGetChannel(parentId, landUUID, out channelId, out channelUri))
  631. m_log.DebugFormat("[VivoxVoice] Found existing channel at " + channelUri);
  632. else if (VivoxTryCreateChannel(parentId, landUUID, landName, out channelUri))
  633. m_log.DebugFormat("[VivoxVoice] Created new channel at " + channelUri);
  634. else
  635. throw new Exception("vivox channel uri not available");
  636. m_log.DebugFormat("[VivoxVoice]: Region:Parcel \"{0}\": parent channel id {1}: retrieved parcel channel_uri {2} ",
  637. landName, parentId, channelUri);
  638. }
  639. return channelUri;
  640. }
  641. private static readonly string m_vivoxLoginPath = "http://{0}/api2/viv_signin.php?userid={1}&pwd={2}";
  642. /// <summary>
  643. /// Perform administrative login for Vivox.
  644. /// Returns a hash table containing values returned from the request.
  645. /// </summary>
  646. private XmlElement VivoxLogin(string name, string password)
  647. {
  648. string requrl = String.Format(m_vivoxLoginPath, m_vivoxServer, name, password);
  649. return VivoxCall(requrl, false);
  650. }
  651. private static readonly string m_vivoxLogoutPath = "http://{0}/api2/viv_signout.php?auth_token={1}";
  652. /// <summary>
  653. /// Perform administrative logout for Vivox.
  654. /// </summary>
  655. private XmlElement VivoxLogout()
  656. {
  657. string requrl = String.Format(m_vivoxLogoutPath, m_vivoxServer, m_authToken);
  658. return VivoxCall(requrl, false);
  659. }
  660. private static readonly string m_vivoxGetAccountPath = "http://{0}/api2/viv_get_acct.php?auth_token={1}&user_name={2}";
  661. /// <summary>
  662. /// Retrieve account information for the specified user.
  663. /// Returns a hash table containing values returned from the request.
  664. /// </summary>
  665. private XmlElement VivoxGetAccountInfo(string user)
  666. {
  667. string requrl = String.Format(m_vivoxGetAccountPath, m_vivoxServer, m_authToken, user);
  668. return VivoxCall(requrl, true);
  669. }
  670. private static readonly string m_vivoxNewAccountPath = "http://{0}/api2/viv_adm_acct_new.php?username={1}&pwd={2}&auth_token={3}";
  671. /// <summary>
  672. /// Creates a new account.
  673. /// For now we supply the minimum set of values, which
  674. /// is user name and password. We *can* supply a lot more
  675. /// demographic data.
  676. /// </summary>
  677. private XmlElement VivoxCreateAccount(string user, string password)
  678. {
  679. string requrl = String.Format(m_vivoxNewAccountPath, m_vivoxServer, user, password, m_authToken);
  680. return VivoxCall(requrl, true);
  681. }
  682. private static readonly string m_vivoxPasswordPath = "http://{0}/api2/viv_adm_password.php?user_name={1}&new_pwd={2}&auth_token={3}";
  683. /// <summary>
  684. /// Change the user's password.
  685. /// </summary>
  686. private XmlElement VivoxPassword(string user, string password)
  687. {
  688. string requrl = String.Format(m_vivoxPasswordPath, m_vivoxServer, user, password, m_authToken);
  689. return VivoxCall(requrl, true);
  690. }
  691. private static readonly string m_vivoxChannelPath = "http://{0}/api2/viv_chan_mod.php?mode={1}&chan_name={2}&auth_token={3}";
  692. /// <summary>
  693. /// Create a channel.
  694. /// Once again, there a multitude of options possible. In the simplest case
  695. /// we specify only the name and get a non-persistent cannel in return. Non
  696. /// persistent means that the channel gets deleted if no-one uses it for
  697. /// 5 hours. To accomodate future requirements, it may be a good idea to
  698. /// initially create channels under the umbrella of a parent ID based upon
  699. /// the region name. That way we have a context for side channels, if those
  700. /// are required in a later phase.
  701. ///
  702. /// In this case the call handles parent and description as optional values.
  703. /// </summary>
  704. private bool VivoxTryCreateChannel(string parent, string channelId, string description, out string channelUri)
  705. {
  706. string requrl = String.Format(m_vivoxChannelPath, m_vivoxServer, "create", channelId, m_authToken);
  707. if (!string.IsNullOrEmpty(parent))
  708. {
  709. requrl = String.Format("{0}&chan_parent={1}", requrl, parent);
  710. }
  711. if (!string.IsNullOrEmpty(description))
  712. {
  713. requrl = String.Format("{0}&chan_desc={1}", requrl, description);
  714. }
  715. requrl = String.Format("{0}&chan_type={1}", requrl, m_vivoxChannelType);
  716. requrl = String.Format("{0}&chan_mode={1}", requrl, m_vivoxChannelMode);
  717. requrl = String.Format("{0}&chan_roll_off={1}", requrl, m_vivoxChannelRollOff);
  718. requrl = String.Format("{0}&chan_dist_model={1}", requrl, m_vivoxChannelDistanceModel);
  719. requrl = String.Format("{0}&chan_max_range={1}", requrl, m_vivoxChannelMaximumRange);
  720. requrl = String.Format("{0}&chan_clamping_distance={1}", requrl, m_vivoxChannelClampingDistance);
  721. XmlElement resp = VivoxCall(requrl, true);
  722. if (XmlFind(resp, "response.level0.body.chan_uri", out channelUri))
  723. return true;
  724. channelUri = String.Empty;
  725. return false;
  726. }
  727. /// <summary>
  728. /// Create a directory.
  729. /// Create a channel with an unconditional type of "dir" (indicating directory).
  730. /// This is used to create an arbitrary name tree for partitioning of the
  731. /// channel name space.
  732. /// The parent and description are optional values.
  733. /// </summary>
  734. private bool VivoxTryCreateDirectory(string dirId, string description, out string channelId)
  735. {
  736. string requrl = String.Format(m_vivoxChannelPath, m_vivoxServer, "create", dirId, m_authToken);
  737. // if (parent != null && parent != String.Empty)
  738. // {
  739. // requrl = String.Format("{0}&chan_parent={1}", requrl, parent);
  740. // }
  741. if (!string.IsNullOrEmpty(description))
  742. {
  743. requrl = String.Format("{0}&chan_desc={1}", requrl, description);
  744. }
  745. requrl = String.Format("{0}&chan_type={1}", requrl, "dir");
  746. XmlElement resp = VivoxCall(requrl, true);
  747. if (IsOK(resp) && XmlFind(resp, "response.level0.body.chan_id", out channelId))
  748. return true;
  749. channelId = String.Empty;
  750. return false;
  751. }
  752. private static readonly string m_vivoxChannelSearchPath = "http://{0}/api2/viv_chan_search.php?cond_channame={1}&auth_token={2}";
  753. /// <summary>
  754. /// Retrieve a channel.
  755. /// Once again, there a multitude of options possible. In the simplest case
  756. /// we specify only the name and get a non-persistent cannel in return. Non
  757. /// persistent means that the channel gets deleted if no-one uses it for
  758. /// 5 hours. To accomodate future requirements, it may be a good idea to
  759. /// initially create channels under the umbrella of a parent ID based upon
  760. /// the region name. That way we have a context for side channels, if those
  761. /// are required in a later phase.
  762. /// In this case the call handles parent and description as optional values.
  763. /// </summary>
  764. private bool VivoxTryGetChannel(string channelParent, string channelName,
  765. out string channelId, out string channelUri)
  766. {
  767. string count;
  768. string requrl = String.Format(m_vivoxChannelSearchPath, m_vivoxServer, channelName, m_authToken);
  769. XmlElement resp = VivoxCall(requrl, true);
  770. if (XmlFind(resp, "response.level0.channel-search.count", out count))
  771. {
  772. int channels = Convert.ToInt32(count);
  773. // Bug in Vivox Server r2978 where count returns 0
  774. // Found by Adam
  775. if (channels == 0)
  776. {
  777. for (int j=0;j<100;j++)
  778. {
  779. string tmpId;
  780. if (!XmlFind(resp, "response.level0.channel-search.channels.channels.level4.id", j, out tmpId))
  781. break;
  782. channels = j + 1;
  783. }
  784. }
  785. for (int i = 0; i < channels; i++)
  786. {
  787. string name;
  788. string id;
  789. string type;
  790. string uri;
  791. string parent;
  792. // skip if not a channel
  793. if (!XmlFind(resp, "response.level0.channel-search.channels.channels.level4.type", i, out type) ||
  794. (type != "channel" && type != "positional_M"))
  795. {
  796. m_log.Debug("[VivoxVoice] Skipping Channel " + i + " as it's not a channel.");
  797. continue;
  798. }
  799. // skip if not the name we are looking for
  800. if (!XmlFind(resp, "response.level0.channel-search.channels.channels.level4.name", i, out name) ||
  801. name != channelName)
  802. {
  803. m_log.Debug("[VivoxVoice] Skipping Channel " + i + " as it has no name.");
  804. continue;
  805. }
  806. // skip if parent does not match
  807. if (channelParent != null && !XmlFind(resp, "response.level0.channel-search.channels.channels.level4.parent", i, out parent))
  808. {
  809. m_log.Debug("[VivoxVoice] Skipping Channel " + i + "/" + name + " as it's parent doesnt match");
  810. continue;
  811. }
  812. // skip if no channel id available
  813. if (!XmlFind(resp, "response.level0.channel-search.channels.channels.level4.id", i, out id))
  814. {
  815. m_log.Debug("[VivoxVoice] Skipping Channel " + i + "/" + name + " as it has no channel ID");
  816. continue;
  817. }
  818. // skip if no channel uri available
  819. if (!XmlFind(resp, "response.level0.channel-search.channels.channels.level4.uri", i, out uri))
  820. {
  821. m_log.Debug("[VivoxVoice] Skipping Channel " + i + "/" + name + " as it has no channel URI");
  822. continue;
  823. }
  824. channelId = id;
  825. channelUri = uri;
  826. return true;
  827. }
  828. }
  829. else
  830. {
  831. m_log.Debug("[VivoxVoice] No count element?");
  832. }
  833. channelId = String.Empty;
  834. channelUri = String.Empty;
  835. // Useful incase something goes wrong.
  836. //m_log.Debug("[VivoxVoice] Could not find channel in XMLRESP: " + resp.InnerXml);
  837. return false;
  838. }
  839. private bool VivoxTryGetDirectory(string directoryName, out string directoryId)
  840. {
  841. string count;
  842. string requrl = String.Format(m_vivoxChannelSearchPath, m_vivoxServer, directoryName, m_authToken);
  843. XmlElement resp = VivoxCall(requrl, true);
  844. if (XmlFind(resp, "response.level0.channel-search.count", out count))
  845. {
  846. int channels = Convert.ToInt32(count);
  847. for (int i = 0; i < channels; i++)
  848. {
  849. string name;
  850. string id;
  851. string type;
  852. // skip if not a directory
  853. if (!XmlFind(resp, "response.level0.channel-search.channels.channels.level4.type", i, out type) ||
  854. type != "dir")
  855. continue;
  856. // skip if not the name we are looking for
  857. if (!XmlFind(resp, "response.level0.channel-search.channels.channels.level4.name", i, out name) ||
  858. name != directoryName)
  859. continue;
  860. // skip if no channel id available
  861. if (!XmlFind(resp, "response.level0.channel-search.channels.channels.level4.id", i, out id))
  862. continue;
  863. directoryId = id;
  864. return true;
  865. }
  866. }
  867. directoryId = String.Empty;
  868. return false;
  869. }
  870. // private static readonly string m_vivoxChannelById = "http://{0}/api2/viv_chan_mod.php?mode={1}&chan_id={2}&auth_token={3}";
  871. // private XmlElement VivoxGetChannelById(string parent, string channelid)
  872. // {
  873. // string requrl = String.Format(m_vivoxChannelById, m_vivoxServer, "get", channelid, m_authToken);
  874. // if (parent != null && parent != String.Empty)
  875. // return VivoxGetChild(parent, channelid);
  876. // else
  877. // return VivoxCall(requrl, true);
  878. // }
  879. private static readonly string m_vivoxChannelDel = "http://{0}/api2/viv_chan_mod.php?mode={1}&chan_id={2}&auth_token={3}";
  880. /// <summary>
  881. /// Delete a channel.
  882. /// Once again, there a multitude of options possible. In the simplest case
  883. /// we specify only the name and get a non-persistent cannel in return. Non
  884. /// persistent means that the channel gets deleted if no-one uses it for
  885. /// 5 hours. To accomodate future requirements, it may be a good idea to
  886. /// initially create channels under the umbrella of a parent ID based upon
  887. /// the region name. That way we have a context for side channels, if those
  888. /// are required in a later phase.
  889. /// In this case the call handles parent and description as optional values.
  890. /// </summary>
  891. private XmlElement VivoxDeleteChannel(string parent, string channelid)
  892. {
  893. string requrl = String.Format(m_vivoxChannelDel, m_vivoxServer, "delete", channelid, m_authToken);
  894. if (!string.IsNullOrEmpty(parent))
  895. {
  896. requrl = String.Format("{0}&chan_parent={1}", requrl, parent);
  897. }
  898. return VivoxCall(requrl, true);
  899. }
  900. private static readonly string m_vivoxChannelSearch = "http://{0}/api2/viv_chan_search.php?&cond_chanparent={1}&auth_token={2}";
  901. /// <summary>
  902. /// Return information on channels in the given directory
  903. /// </summary>
  904. private XmlElement VivoxListChildren(string channelid)
  905. {
  906. string requrl = String.Format(m_vivoxChannelSearch, m_vivoxServer, channelid, m_authToken);
  907. return VivoxCall(requrl, true);
  908. }
  909. // private XmlElement VivoxGetChild(string parent, string child)
  910. // {
  911. // XmlElement children = VivoxListChildren(parent);
  912. // string count;
  913. // if (XmlFind(children, "response.level0.channel-search.count", out count))
  914. // {
  915. // int cnum = Convert.ToInt32(count);
  916. // for (int i = 0; i < cnum; i++)
  917. // {
  918. // string name;
  919. // string id;
  920. // if (XmlFind(children, "response.level0.channel-search.channels.channels.level4.name", i, out name))
  921. // {
  922. // if (name == child)
  923. // {
  924. // if (XmlFind(children, "response.level0.channel-search.channels.channels.level4.id", i, out id))
  925. // {
  926. // return VivoxGetChannelById(null, id);
  927. // }
  928. // }
  929. // }
  930. // }
  931. // }
  932. // // One we *know* does not exist.
  933. // return VivoxGetChannel(null, Guid.NewGuid().ToString());
  934. // }
  935. /// <summary>
  936. /// This method handles the WEB side of making a request over the
  937. /// Vivox interface. The returned values are tansferred to a has
  938. /// table which is returned as the result.
  939. /// The outcome of the call can be determined by examining the
  940. /// status value in the hash table.
  941. /// </summary>
  942. private XmlElement VivoxCall(string requrl, bool admin)
  943. {
  944. XmlDocument doc = null;
  945. // If this is an admin call, and admin is not connected,
  946. // and the admin id cannot be connected, then fail.
  947. if (admin && !m_adminConnected && !DoAdminLogin())
  948. return null;
  949. doc = new XmlDocument();
  950. // Let's serialize all calls to Vivox. Most of these are driven by
  951. // the clients (CAPs), when the user arrives at the region. We don't
  952. // want to issue many simultaneous http requests to Vivox, because mono
  953. // doesn't like that
  954. lock (m_Lock)
  955. {
  956. try
  957. {
  958. // Otherwise prepare the request
  959. m_log.DebugFormat("[VivoxVoice] Sending request <{0}>", requrl);
  960. HttpWebRequest req = (HttpWebRequest)WebRequest.Create(requrl);
  961. // We are sending just parameters, no content
  962. req.ContentLength = 0;
  963. // Send request and retrieve the response
  964. using (HttpWebResponse rsp = (HttpWebResponse)req.GetResponse())
  965. using (Stream s = rsp.GetResponseStream())
  966. using (XmlTextReader rdr = new XmlTextReader(s))
  967. doc.Load(rdr);
  968. }
  969. catch (Exception e)
  970. {
  971. m_log.ErrorFormat("[VivoxVoice] Error in admin call : {0}", e.Message);
  972. }
  973. }
  974. // If we're debugging server responses, dump the whole
  975. // load now
  976. if (m_dumpXml) XmlScanl(doc.DocumentElement,0);
  977. return doc.DocumentElement;
  978. }
  979. /// <summary>
  980. /// Just say if it worked.
  981. /// </summary>
  982. private bool IsOK(XmlElement resp)
  983. {
  984. string status;
  985. XmlFind(resp, "response.level0.status", out status);
  986. return (status == "OK");
  987. }
  988. /// <summary>
  989. /// Login has been factored in this way because it gets called
  990. /// from several places in the module, and we want it to work
  991. /// the same way each time.
  992. /// </summary>
  993. private bool DoAdminLogin()
  994. {
  995. m_log.Debug("[VivoxVoice] Establishing admin connection");
  996. lock (vlock)
  997. {
  998. if (!m_adminConnected)
  999. {
  1000. string status = "Unknown";
  1001. XmlElement resp = null;
  1002. resp = VivoxLogin(m_vivoxAdminUser, m_vivoxAdminPassword);
  1003. if (XmlFind(resp, "response.level0.body.status", out status))
  1004. {
  1005. if (status == "Ok")
  1006. {
  1007. m_log.Info("[VivoxVoice] Admin connection established");
  1008. if (XmlFind(resp, "response.level0.body.auth_token", out m_authToken))
  1009. {
  1010. if (m_dumpXml) m_log.DebugFormat("[VivoxVoice] Auth Token <{0}>",
  1011. m_authToken);
  1012. m_adminConnected = true;
  1013. }
  1014. }
  1015. else
  1016. {
  1017. m_log.WarnFormat("[VivoxVoice] Admin connection failed, status = {0}",
  1018. status);
  1019. }
  1020. }
  1021. }
  1022. }
  1023. return m_adminConnected;
  1024. }
  1025. /// <summary>
  1026. /// The XmlScan routine is provided to aid in the
  1027. /// reverse engineering of incompletely
  1028. /// documented packets returned by the Vivox
  1029. /// voice server. It is only called if the
  1030. /// m_dumpXml switch is set.
  1031. /// </summary>
  1032. private void XmlScanl(XmlElement e, int index)
  1033. {
  1034. if (e.HasChildNodes)
  1035. {
  1036. m_log.DebugFormat("<{0}>".PadLeft(index+5), e.Name);
  1037. XmlNodeList children = e.ChildNodes;
  1038. foreach (XmlNode node in children)
  1039. switch (node.NodeType)
  1040. {
  1041. case XmlNodeType.Element :
  1042. XmlScanl((XmlElement)node, index+1);
  1043. break;
  1044. case XmlNodeType.Text :
  1045. m_log.DebugFormat("\"{0}\"".PadLeft(index+5), node.Value);
  1046. break;
  1047. default :
  1048. break;
  1049. }
  1050. m_log.DebugFormat("</{0}>".PadLeft(index+6), e.Name);
  1051. }
  1052. else
  1053. {
  1054. m_log.DebugFormat("<{0}/>".PadLeft(index+6), e.Name);
  1055. }
  1056. }
  1057. private static readonly char[] C_POINT = {'.'};
  1058. /// <summary>
  1059. /// The Find method is passed an element whose
  1060. /// inner text is scanned in an attempt to match
  1061. /// the name hierarchy passed in the 'tag' parameter.
  1062. /// If the whole hierarchy is resolved, the InnerText
  1063. /// value at that point is returned. Note that this
  1064. /// may itself be a subhierarchy of the entire
  1065. /// document. The function returns a boolean indicator
  1066. /// of the search's success. The search is performed
  1067. /// by the recursive Search method.
  1068. /// </summary>
  1069. private bool XmlFind(XmlElement root, string tag, int nth, out string result)
  1070. {
  1071. if (root == null || tag == null || tag == String.Empty)
  1072. {
  1073. result = String.Empty;
  1074. return false;
  1075. }
  1076. return XmlSearch(root,tag.Split(C_POINT),0, ref nth, out result);
  1077. }
  1078. private bool XmlFind(XmlElement root, string tag, out string result)
  1079. {
  1080. int nth = 0;
  1081. if (root == null || tag == null || tag == String.Empty)
  1082. {
  1083. result = String.Empty;
  1084. return false;
  1085. }
  1086. return XmlSearch(root,tag.Split(C_POINT),0, ref nth, out result);
  1087. }
  1088. /// <summary>
  1089. /// XmlSearch is initially called by XmlFind, and then
  1090. /// recursively called by itself until the document
  1091. /// supplied to XmlFind is either exhausted or the name hierarchy
  1092. /// is matched.
  1093. ///
  1094. /// If the hierarchy is matched, the value is returned in
  1095. /// result, and true returned as the function's
  1096. /// value. Otherwise the result is set to the empty string and
  1097. /// false is returned.
  1098. /// </summary>
  1099. private bool XmlSearch(XmlElement e, string[] tags, int index, ref int nth, out string result)
  1100. {
  1101. if (index == tags.Length || e.Name != tags[index])
  1102. {
  1103. result = String.Empty;
  1104. return false;
  1105. }
  1106. if (tags.Length-index == 1)
  1107. {
  1108. if (nth == 0)
  1109. {
  1110. result = e.InnerText;
  1111. return true;
  1112. }
  1113. else
  1114. {
  1115. nth--;
  1116. result = String.Empty;
  1117. return false;
  1118. }
  1119. }
  1120. if (e.HasChildNodes)
  1121. {
  1122. XmlNodeList children = e.ChildNodes;
  1123. foreach (XmlNode node in children)
  1124. {
  1125. switch (node.NodeType)
  1126. {
  1127. case XmlNodeType.Element :
  1128. if (XmlSearch((XmlElement)node, tags, index+1, ref nth, out result))
  1129. return true;
  1130. break;
  1131. default :
  1132. break;
  1133. }
  1134. }
  1135. }
  1136. result = String.Empty;
  1137. return false;
  1138. }
  1139. }
  1140. }