VivoxVoiceModule.cs 58 KB

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