VivoxVoiceModule.cs 62 KB

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