VivoxVoiceModule.cs 59 KB

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