OpenSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros
VivoxVoiceModule.cs
Go to the documentation of this file.
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 
28 using System;
29 using System.IO;
30 using System.Net;
31 using System.Text;
32 using System.Xml;
33 using System.Collections;
34 using System.Collections.Generic;
35 using System.Reflection;
36 using System.Threading;
37 using OpenMetaverse;
38 using log4net;
39 using Mono.Addins;
40 using Nini.Config;
41 using Nwc.XmlRpc;
42 using OpenSim.Framework;
43 
44 using OpenSim.Framework.Capabilities;
45 using OpenSim.Framework.Servers;
46 using OpenSim.Framework.Servers.HttpServer;
47 using OpenSim.Region.Framework.Interfaces;
48 using OpenSim.Region.Framework.Scenes;
50 
51 namespace OpenSim.Region.OptionalModules.Avatar.Voice.VivoxVoice
52 {
53  [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "VivoxVoiceModule")]
55  {
56 
57  // channel distance model values
58  public const int CHAN_DIST_NONE = 0; // no attenuation
59  public const int CHAN_DIST_INVERSE = 1; // inverse distance attenuation
60  public const int CHAN_DIST_LINEAR = 2; // linear attenuation
61  public const int CHAN_DIST_EXPONENT = 3; // exponential attenuation
62  public const int CHAN_DIST_DEFAULT = CHAN_DIST_LINEAR;
63 
64  // channel type values
65  public static readonly string CHAN_TYPE_POSITIONAL = "positional";
66  public static readonly string CHAN_TYPE_CHANNEL = "channel";
67  public static readonly string CHAN_TYPE_DEFAULT = CHAN_TYPE_POSITIONAL;
68 
69  // channel mode values
70  public static readonly string CHAN_MODE_OPEN = "open";
71  public static readonly string CHAN_MODE_LECTURE = "lecture";
72  public static readonly string CHAN_MODE_PRESENTATION = "presentation";
73  public static readonly string CHAN_MODE_AUDITORIUM = "auditorium";
74  public static readonly string CHAN_MODE_DEFAULT = CHAN_MODE_OPEN;
75 
76  // unconstrained default values
77  public const double CHAN_ROLL_OFF_DEFAULT = 2.0; // rate of attenuation
78  public const double CHAN_ROLL_OFF_MIN = 1.0;
79  public const double CHAN_ROLL_OFF_MAX = 4.0;
80  public const int CHAN_MAX_RANGE_DEFAULT = 80; // distance at which channel is silent
81  public const int CHAN_MAX_RANGE_MIN = 0;
82  public const int CHAN_MAX_RANGE_MAX = 160;
83  public const int CHAN_CLAMPING_DISTANCE_DEFAULT = 10; // distance before attenuation applies
84  public const int CHAN_CLAMPING_DISTANCE_MIN = 0;
85  public const int CHAN_CLAMPING_DISTANCE_MAX = 160;
86 
87  // Infrastructure
88  private static readonly ILog m_log =
89  LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
90  private static readonly Object vlock = new Object();
91 
92  // Capability strings
93  private static readonly string m_parcelVoiceInfoRequestPath = "0107/";
94  private static readonly string m_provisionVoiceAccountRequestPath = "0108/";
95  //private static readonly string m_chatSessionRequestPath = "0109/";
96 
97  // Control info, e.g. vivox server, admin user, admin password
98  private static bool m_pluginEnabled = false;
99  private static bool m_adminConnected = false;
100 
101  private static string m_vivoxServer;
102  private static string m_vivoxSipUri;
103  private static string m_vivoxVoiceAccountApi;
104  private static string m_vivoxAdminUser;
105  private static string m_vivoxAdminPassword;
106  private static string m_authToken = String.Empty;
107 
108  private static int m_vivoxChannelDistanceModel;
109  private static double m_vivoxChannelRollOff;
110  private static int m_vivoxChannelMaximumRange;
111  private static string m_vivoxChannelMode;
112  private static string m_vivoxChannelType;
113  private static int m_vivoxChannelClampingDistance;
114 
115  private static Dictionary<string,string> m_parents = new Dictionary<string,string>();
116  private static bool m_dumpXml;
117 
118  private IConfig m_config;
119 
120  private object m_Lock;
121 
122  public void Initialise(IConfigSource config)
123  {
124  MainConsole.Instance.Commands.AddCommand("vivox", false, "vivox debug", "vivox debug <on>|<off>", "Set vivox debugging", HandleDebug);
125 
126  m_config = config.Configs["VivoxVoice"];
127 
128  if (null == m_config)
129  return;
130 
131  if (!m_config.GetBoolean("enabled", false))
132  return;
133 
134  m_Lock = new object();
135 
136  try
137  {
138  // retrieve configuration variables
139  m_vivoxServer = m_config.GetString("vivox_server", String.Empty);
140  m_vivoxSipUri = m_config.GetString("vivox_sip_uri", String.Empty);
141  m_vivoxAdminUser = m_config.GetString("vivox_admin_user", String.Empty);
142  m_vivoxAdminPassword = m_config.GetString("vivox_admin_password", String.Empty);
143 
144  m_vivoxChannelDistanceModel = m_config.GetInt("vivox_channel_distance_model", CHAN_DIST_DEFAULT);
145  m_vivoxChannelRollOff = m_config.GetDouble("vivox_channel_roll_off", CHAN_ROLL_OFF_DEFAULT);
146  m_vivoxChannelMaximumRange = m_config.GetInt("vivox_channel_max_range", CHAN_MAX_RANGE_DEFAULT);
147  m_vivoxChannelMode = m_config.GetString("vivox_channel_mode", CHAN_MODE_DEFAULT).ToLower();
148  m_vivoxChannelType = m_config.GetString("vivox_channel_type", CHAN_TYPE_DEFAULT).ToLower();
149  m_vivoxChannelClampingDistance = m_config.GetInt("vivox_channel_clamping_distance",
150  CHAN_CLAMPING_DISTANCE_DEFAULT);
151  m_dumpXml = m_config.GetBoolean("dump_xml", false);
152 
153  // Validate against constraints and default if necessary
154  if (m_vivoxChannelRollOff < CHAN_ROLL_OFF_MIN || m_vivoxChannelRollOff > CHAN_ROLL_OFF_MAX)
155  {
156  m_log.WarnFormat("[VivoxVoice] Invalid value for roll off ({0}), reset to {1}.",
157  m_vivoxChannelRollOff, CHAN_ROLL_OFF_DEFAULT);
158  m_vivoxChannelRollOff = CHAN_ROLL_OFF_DEFAULT;
159  }
160 
161  if (m_vivoxChannelMaximumRange < CHAN_MAX_RANGE_MIN || m_vivoxChannelMaximumRange > CHAN_MAX_RANGE_MAX)
162  {
163  m_log.WarnFormat("[VivoxVoice] Invalid value for maximum range ({0}), reset to {1}.",
164  m_vivoxChannelMaximumRange, CHAN_MAX_RANGE_DEFAULT);
165  m_vivoxChannelMaximumRange = CHAN_MAX_RANGE_DEFAULT;
166  }
167 
168  if (m_vivoxChannelClampingDistance < CHAN_CLAMPING_DISTANCE_MIN ||
169  m_vivoxChannelClampingDistance > CHAN_CLAMPING_DISTANCE_MAX)
170  {
171  m_log.WarnFormat("[VivoxVoice] Invalid value for clamping distance ({0}), reset to {1}.",
172  m_vivoxChannelClampingDistance, CHAN_CLAMPING_DISTANCE_DEFAULT);
173  m_vivoxChannelClampingDistance = CHAN_CLAMPING_DISTANCE_DEFAULT;
174  }
175 
176  switch (m_vivoxChannelMode)
177  {
178  case "open" : break;
179  case "lecture" : break;
180  case "presentation" : break;
181  case "auditorium" : break;
182  default :
183  m_log.WarnFormat("[VivoxVoice] Invalid value for channel mode ({0}), reset to {1}.",
184  m_vivoxChannelMode, CHAN_MODE_DEFAULT);
185  m_vivoxChannelMode = CHAN_MODE_DEFAULT;
186  break;
187  }
188 
189  switch (m_vivoxChannelType)
190  {
191  case "positional" : break;
192  case "channel" : break;
193  default :
194  m_log.WarnFormat("[VivoxVoice] Invalid value for channel type ({0}), reset to {1}.",
195  m_vivoxChannelType, CHAN_TYPE_DEFAULT);
196  m_vivoxChannelType = CHAN_TYPE_DEFAULT;
197  break;
198  }
199 
200  m_vivoxVoiceAccountApi = String.Format("http://{0}/api2", m_vivoxServer);
201 
202  // Admin interface required values
203  if (String.IsNullOrEmpty(m_vivoxServer) ||
204  String.IsNullOrEmpty(m_vivoxSipUri) ||
205  String.IsNullOrEmpty(m_vivoxAdminUser) ||
206  String.IsNullOrEmpty(m_vivoxAdminPassword))
207  {
208  m_log.Error("[VivoxVoice] plugin mis-configured");
209  m_log.Info("[VivoxVoice] plugin disabled: incomplete configuration");
210  return;
211  }
212 
213  m_log.InfoFormat("[VivoxVoice] using vivox server {0}", m_vivoxServer);
214 
215  // Get admin rights and cleanup any residual channel definition
216 
217  DoAdminLogin();
218 
219  m_pluginEnabled = true;
220 
221  m_log.Info("[VivoxVoice] plugin enabled");
222  }
223  catch (Exception e)
224  {
225  m_log.ErrorFormat("[VivoxVoice] plugin initialization failed: {0}", e.Message);
226  m_log.DebugFormat("[VivoxVoice] plugin initialization failed: {0}", e.ToString());
227  return;
228  }
229  }
230 
231  public void AddRegion(Scene scene)
232  {
233  if (m_pluginEnabled)
234  {
235  lock (vlock)
236  {
237  string channelId;
238 
239  string sceneUUID = scene.RegionInfo.RegionID.ToString();
240  string sceneName = scene.RegionInfo.RegionName;
241 
242  // Make sure that all local channels are deleted.
243  // So we have to search for the children, and then do an
244  // iteration over the set of chidren identified.
245  // This assumes that there is just one directory per
246  // region.
247 
248  if (VivoxTryGetDirectory(sceneUUID + "D", out channelId))
249  {
250  m_log.DebugFormat("[VivoxVoice]: region {0}: uuid {1}: located directory id {2}",
251  sceneName, sceneUUID, channelId);
252 
253  XmlElement children = VivoxListChildren(channelId);
254  string count;
255 
256  if (XmlFind(children, "response.level0.channel-search.count", out count))
257  {
258  int cnum = Convert.ToInt32(count);
259  for (int i = 0; i < cnum; i++)
260  {
261  string id;
262  if (XmlFind(children, "response.level0.channel-search.channels.channels.level4.id", i, out id))
263  {
264  if (!IsOK(VivoxDeleteChannel(channelId, id)))
265  m_log.WarnFormat("[VivoxVoice] Channel delete failed {0}:{1}:{2}", i, channelId, id);
266  }
267  }
268  }
269  }
270  else
271  {
272  if (!VivoxTryCreateDirectory(sceneUUID + "D", sceneName, out channelId))
273  {
274  m_log.WarnFormat("[VivoxVoice] Create failed <{0}:{1}:{2}>",
275  "*", sceneUUID, sceneName);
276  channelId = String.Empty;
277  }
278  }
279 
280  // Create a dictionary entry unconditionally. This eliminates the
281  // need to check for a parent in the core code. The end result is
282  // the same, if the parent table entry is an empty string, then
283  // region channels will be created as first-level channels.
284  lock (m_parents)
285  {
286  if (m_parents.ContainsKey(sceneUUID))
287  {
288  RemoveRegion(scene);
289  m_parents.Add(sceneUUID, channelId);
290  }
291  else
292  {
293  m_parents.Add(sceneUUID, channelId);
294  }
295  }
296  }
297 
298  // we need to capture scene in an anonymous method
299  // here as we need it later in the callbacks
300  scene.EventManager.OnRegisterCaps += delegate(UUID agentID, Caps caps)
301  {
302  OnRegisterCaps(scene, agentID, caps);
303  };
304  }
305  }
306 
307  public void RegionLoaded(Scene scene)
308  {
309  // Do nothing.
310  }
311 
312  public void RemoveRegion(Scene scene)
313  {
314  if (m_pluginEnabled)
315  {
316  lock (vlock)
317  {
318  string channelId;
319 
320  string sceneUUID = scene.RegionInfo.RegionID.ToString();
321  string sceneName = scene.RegionInfo.RegionName;
322 
323  // Make sure that all local channels are deleted.
324  // So we have to search for the children, and then do an
325  // iteration over the set of chidren identified.
326  // This assumes that there is just one directory per
327  // region.
328  if (VivoxTryGetDirectory(sceneUUID + "D", out channelId))
329  {
330  m_log.DebugFormat("[VivoxVoice]: region {0}: uuid {1}: located directory id {2}",
331  sceneName, sceneUUID, channelId);
332 
333  XmlElement children = VivoxListChildren(channelId);
334  string count;
335 
336  if (XmlFind(children, "response.level0.channel-search.count", out count))
337  {
338  int cnum = Convert.ToInt32(count);
339  for (int i = 0; i < cnum; i++)
340  {
341  string id;
342  if (XmlFind(children, "response.level0.channel-search.channels.channels.level4.id", i, out id))
343  {
344  if (!IsOK(VivoxDeleteChannel(channelId, id)))
345  m_log.WarnFormat("[VivoxVoice] Channel delete failed {0}:{1}:{2}", i, channelId, id);
346  }
347  }
348  }
349  }
350 
351  if (!IsOK(VivoxDeleteChannel(null, channelId)))
352  m_log.WarnFormat("[VivoxVoice] Parent channel delete failed {0}:{1}:{2}", sceneName, sceneUUID, channelId);
353 
354  // Remove the channel umbrella entry
355 
356  lock (m_parents)
357  {
358  if (m_parents.ContainsKey(sceneUUID))
359  {
360  m_parents.Remove(sceneUUID);
361  }
362  }
363  }
364  }
365  }
366 
367  public void PostInitialise()
368  {
369  // Do nothing.
370  }
371 
372  public void Close()
373  {
374  if (m_pluginEnabled)
375  VivoxLogout();
376  }
377 
378  public Type ReplaceableInterface
379  {
380  get { return null; }
381  }
382 
383  public string Name
384  {
385  get { return "VivoxVoiceModule"; }
386  }
387 
388  public bool IsSharedModule
389  {
390  get { return true; }
391  }
392 
393  // <summary>
394  // OnRegisterCaps is invoked via the scene.EventManager
395  // everytime OpenSim hands out capabilities to a client
396  // (login, region crossing). We contribute two capabilities to
397  // the set of capabilities handed back to the client:
398  // ProvisionVoiceAccountRequest and ParcelVoiceInfoRequest.
399  //
400  // ProvisionVoiceAccountRequest allows the client to obtain
401  // the voice account credentials for the avatar it is
402  // controlling (e.g., user name, password, etc).
403  //
404  // ParcelVoiceInfoRequest is invoked whenever the client
405  // changes from one region or parcel to another.
406  //
407  // Note that OnRegisterCaps is called here via a closure
408  // delegate containing the scene of the respective region (see
409  // Initialise()).
410  // </summary>
411  public void OnRegisterCaps(Scene scene, UUID agentID, Caps caps)
412  {
413  m_log.DebugFormat("[VivoxVoice] OnRegisterCaps: agentID {0} caps {1}", agentID, caps);
414 
415  string capsBase = "/CAPS/" + caps.CapsObjectPath;
416 
417  caps.RegisterHandler(
418  "ProvisionVoiceAccountRequest",
419  new RestStreamHandler(
420  "POST",
421  capsBase + m_provisionVoiceAccountRequestPath,
422  (request, path, param, httpRequest, httpResponse)
423  => ProvisionVoiceAccountRequest(scene, request, path, param, agentID, caps),
424  "ProvisionVoiceAccountRequest",
425  agentID.ToString()));
426 
427  caps.RegisterHandler(
428  "ParcelVoiceInfoRequest",
429  new RestStreamHandler(
430  "POST",
431  capsBase + m_parcelVoiceInfoRequestPath,
432  (request, path, param, httpRequest, httpResponse)
433  => ParcelVoiceInfoRequest(scene, request, path, param, agentID, caps),
434  "ParcelVoiceInfoRequest",
435  agentID.ToString()));
436 
437  //caps.RegisterHandler(
438  // "ChatSessionRequest",
439  // new RestStreamHandler(
440  // "POST",
441  // capsBase + m_chatSessionRequestPath,
442  // (request, path, param, httpRequest, httpResponse)
443  // => ChatSessionRequest(scene, request, path, param, agentID, caps),
444  // "ChatSessionRequest",
445  // agentID.ToString()));
446  }
447 
458  public string ProvisionVoiceAccountRequest(Scene scene, string request, string path, string param,
459  UUID agentID, Caps caps)
460  {
461  try
462  {
463  ScenePresence avatar = null;
464  string avatarName = null;
465 
466  if (scene == null)
467  throw new Exception("[VivoxVoice][PROVISIONVOICE]: Invalid scene");
468 
469  avatar = scene.GetScenePresence(agentID);
470  while (avatar == null)
471  {
472  Thread.Sleep(100);
473  avatar = scene.GetScenePresence(agentID);
474  }
475 
476  avatarName = avatar.Name;
477 
478  m_log.DebugFormat("[VivoxVoice][PROVISIONVOICE]: scene = {0}, agentID = {1}", scene, agentID);
479 // m_log.DebugFormat("[VivoxVoice][PROVISIONVOICE]: request: {0}, path: {1}, param: {2}",
480 // request, path, param);
481 
482  XmlElement resp;
483  bool retry = false;
484  string agentname = "x" + Convert.ToBase64String(agentID.GetBytes());
485  string password = new UUID(Guid.NewGuid()).ToString().Replace('-','Z').Substring(0,16);
486  string code = String.Empty;
487 
488  agentname = agentname.Replace('+', '-').Replace('/', '_');
489 
490  do
491  {
492  resp = VivoxGetAccountInfo(agentname);
493 
494  if (XmlFind(resp, "response.level0.status", out code))
495  {
496  if (code != "OK")
497  {
498  if (XmlFind(resp, "response.level0.body.code", out code))
499  {
500  // If the request was recognized, then this should be set to something
501  switch (code)
502  {
503  case "201" : // Account expired
504  m_log.ErrorFormat("[VivoxVoice]: avatar \"{0}\": Get account information failed : expired credentials",
505  avatarName);
506  m_adminConnected = false;
507  retry = DoAdminLogin();
508  break;
509 
510  case "202" : // Missing credentials
511  m_log.ErrorFormat("[VivoxVoice]: avatar \"{0}\": Get account information failed : missing credentials",
512  avatarName);
513  break;
514 
515  case "212" : // Not authorized
516  m_log.ErrorFormat("[VivoxVoice]: avatar \"{0}\": Get account information failed : not authorized",
517  avatarName);
518  break;
519 
520  case "300" : // Required parameter missing
521  m_log.ErrorFormat("[VivoxVoice]: avatar \"{0}\": Get account information failed : parameter missing",
522  avatarName);
523  break;
524 
525  case "403" : // Account does not exist
526  resp = VivoxCreateAccount(agentname,password);
527  // Note: This REALLY MUST BE status. Create Account does not return code.
528  if (XmlFind(resp, "response.level0.status", out code))
529  {
530  switch (code)
531  {
532  case "201" : // Account expired
533  m_log.ErrorFormat("[VivoxVoice]: avatar \"{0}\": Create account information failed : expired credentials",
534  avatarName);
535  m_adminConnected = false;
536  retry = DoAdminLogin();
537  break;
538 
539  case "202" : // Missing credentials
540  m_log.ErrorFormat("[VivoxVoice]: avatar \"{0}\": Create account information failed : missing credentials",
541  avatarName);
542  break;
543 
544  case "212" : // Not authorized
545  m_log.ErrorFormat("[VivoxVoice]: avatar \"{0}\": Create account information failed : not authorized",
546  avatarName);
547  break;
548 
549  case "300" : // Required parameter missing
550  m_log.ErrorFormat("[VivoxVoice]: avatar \"{0}\": Create account information failed : parameter missing",
551  avatarName);
552  break;
553 
554  case "400" : // Create failed
555  m_log.ErrorFormat("[VivoxVoice]: avatar \"{0}\": Create account information failed : create failed",
556  avatarName);
557  break;
558  }
559  }
560  break;
561 
562  case "404" : // Failed to retrieve account
563  m_log.ErrorFormat("[VivoxVoice]: avatar \"{0}\": Get account information failed : retrieve failed");
564  // [AMW] Sleep and retry for a fixed period? Or just abandon?
565  break;
566  }
567  }
568  }
569  }
570  }
571  while (retry);
572 
573  if (code != "OK")
574  {
575  m_log.DebugFormat("[VivoxVoice][PROVISIONVOICE]: Get Account Request failed for \"{0}\"", avatarName);
576  throw new Exception("Unable to execute request");
577  }
578 
579  // Unconditionally change the password on each request
580  VivoxPassword(agentname, password);
581 
582  LLSDVoiceAccountResponse voiceAccountResponse =
583  new LLSDVoiceAccountResponse(agentname, password, m_vivoxSipUri, m_vivoxVoiceAccountApi);
584 
585  string r = LLSDHelpers.SerialiseLLSDReply(voiceAccountResponse);
586 
587 // m_log.DebugFormat("[VivoxVoice][PROVISIONVOICE]: avatar \"{0}\": {1}", avatarName, r);
588 
589  return r;
590  }
591  catch (Exception e)
592  {
593  m_log.ErrorFormat("[VivoxVoice][PROVISIONVOICE]: : {0}, retry later", e.Message);
594  m_log.DebugFormat("[VivoxVoice][PROVISIONVOICE]: : {0} failed", e.ToString());
595  return "<llsd><undef /></llsd>";
596  }
597  }
598 
609  public string ParcelVoiceInfoRequest(Scene scene, string request, string path, string param,
610  UUID agentID, Caps caps)
611  {
612  ScenePresence avatar = scene.GetScenePresence(agentID);
613  string avatarName = avatar.Name;
614 
615  // - check whether we have a region channel in our cache
616  // - if not:
617  // create it and cache it
618  // - send it to the client
619  // - send channel_uri: as "sip:regionID@m_sipDomain"
620  try
621  {
622  LLSDParcelVoiceInfoResponse parcelVoiceInfo;
623  string channel_uri;
624 
625  if (null == scene.LandChannel)
626  throw new Exception(String.Format("region \"{0}\": avatar \"{1}\": land data not yet available",
627  scene.RegionInfo.RegionName, avatarName));
628 
629  // get channel_uri: check first whether estate
630  // settings allow voice, then whether parcel allows
631  // voice, if all do retrieve or obtain the parcel
632  // voice channel
633  LandData land = scene.GetLandData(avatar.AbsolutePosition);
634  if (land == null)
635  {
636  return "<llsd><undef /></llsd>";
637  }
638 
639 // m_log.DebugFormat("[VivoxVoice][PARCELVOICE]: region \"{0}\": Parcel \"{1}\" ({2}): avatar \"{3}\": request: {4}, path: {5}, param: {6}",
640 // scene.RegionInfo.RegionName, land.Name, land.LocalID, avatarName, request, path, param);
641  // m_log.DebugFormat("[VivoxVoice][PARCELVOICE]: avatar \"{0}\": location: {1} {2} {3}",
642  // avatarName, avatar.AbsolutePosition.X, avatar.AbsolutePosition.Y, avatar.AbsolutePosition.Z);
643 
644  // TODO: EstateSettings don't seem to get propagated...
646  {
647  //m_log.DebugFormat("[VivoxVoice][PARCELVOICE]: region \"{0}\": voice not enabled in estate settings",
648  // scene.RegionInfo.RegionName);
649  channel_uri = String.Empty;
650  }
651 
652  if ((land.Flags & (uint)ParcelFlags.AllowVoiceChat) == 0)
653  {
654  //m_log.DebugFormat("[VivoxVoice][PARCELVOICE]: region \"{0}\": Parcel \"{1}\" ({2}): avatar \"{3}\": voice not enabled for parcel",
655  // scene.RegionInfo.RegionName, land.Name, land.LocalID, avatarName);
656  channel_uri = String.Empty;
657  }
658  else
659  {
660  channel_uri = RegionGetOrCreateChannel(scene, land);
661  }
662 
663  // fill in our response to the client
664  Hashtable creds = new Hashtable();
665  creds["channel_uri"] = channel_uri;
666 
667  parcelVoiceInfo = new LLSDParcelVoiceInfoResponse(scene.RegionInfo.RegionName, land.LocalID, creds);
668  string r = LLSDHelpers.SerialiseLLSDReply(parcelVoiceInfo);
669 
670 // m_log.DebugFormat("[VivoxVoice][PARCELVOICE]: region \"{0}\": Parcel \"{1}\" ({2}): avatar \"{3}\": {4}",
671 // scene.RegionInfo.RegionName, land.Name, land.LocalID, avatarName, r);
672  return r;
673  }
674  catch (Exception e)
675  {
676  m_log.ErrorFormat("[VivoxVoice][PARCELVOICE]: region \"{0}\": avatar \"{1}\": {2}, retry later",
677  scene.RegionInfo.RegionName, avatarName, e.Message);
678  m_log.DebugFormat("[VivoxVoice][PARCELVOICE]: region \"{0}\": avatar \"{1}\": {2} failed",
679  scene.RegionInfo.RegionName, avatarName, e.ToString());
680 
681  return "<llsd><undef /></llsd>";
682  }
683  }
684 
695  public string ChatSessionRequest(Scene scene, string request, string path, string param,
696  UUID agentID, Caps caps)
697  {
698 // ScenePresence avatar = scene.GetScenePresence(agentID);
699 // string avatarName = avatar.Name;
700 
701 // m_log.DebugFormat("[VivoxVoice][CHATSESSION]: avatar \"{0}\": request: {1}, path: {2}, param: {3}",
702 // avatarName, request, path, param);
703  return "<llsd>true</llsd>";
704  }
705 
706  private string RegionGetOrCreateChannel(Scene scene, LandData land)
707  {
708  string channelUri = null;
709  string channelId = null;
710 
711  string landUUID;
712  string landName;
713  string parentId;
714 
715  lock (m_parents)
716  parentId = m_parents[scene.RegionInfo.RegionID.ToString()];
717 
718  // Create parcel voice channel. If no parcel exists, then the voice channel ID is the same
719  // as the directory ID. Otherwise, it reflects the parcel's ID.
720  if (land.LocalID != 1 && (land.Flags & (uint)ParcelFlags.UseEstateVoiceChan) == 0)
721  {
722  landName = String.Format("{0}:{1}", scene.RegionInfo.RegionName, land.Name);
723  landUUID = land.GlobalID.ToString();
724  m_log.DebugFormat("[VivoxVoice]: Region:Parcel \"{0}\": parcel id {1}: using channel name {2}",
725  landName, land.LocalID, landUUID);
726  }
727  else
728  {
729  landName = String.Format("{0}:{1}", scene.RegionInfo.RegionName, scene.RegionInfo.RegionName);
730  landUUID = scene.RegionInfo.RegionID.ToString();
731  m_log.DebugFormat("[VivoxVoice]: Region:Parcel \"{0}\": parcel id {1}: using channel name {2}",
732  landName, land.LocalID, landUUID);
733  }
734 
735  lock (vlock)
736  {
737  // Added by Adam to help debug channel not availible errors.
738  if (VivoxTryGetChannel(parentId, landUUID, out channelId, out channelUri))
739  m_log.DebugFormat("[VivoxVoice] Found existing channel at " + channelUri);
740  else if (VivoxTryCreateChannel(parentId, landUUID, landName, out channelUri))
741  m_log.DebugFormat("[VivoxVoice] Created new channel at " + channelUri);
742  else
743  throw new Exception("vivox channel uri not available");
744 
745  m_log.DebugFormat("[VivoxVoice]: Region:Parcel \"{0}\": parent channel id {1}: retrieved parcel channel_uri {2} ",
746  landName, parentId, channelUri);
747  }
748 
749  return channelUri;
750  }
751 
752  private static readonly string m_vivoxLoginPath = "http://{0}/api2/viv_signin.php?userid={1}&pwd={2}";
753 
758  private XmlElement VivoxLogin(string name, string password)
759  {
760  string requrl = String.Format(m_vivoxLoginPath, m_vivoxServer, name, password);
761  return VivoxCall(requrl, false);
762  }
763 
764  private static readonly string m_vivoxLogoutPath = "http://{0}/api2/viv_signout.php?auth_token={1}";
765 
769  private XmlElement VivoxLogout()
770  {
771  string requrl = String.Format(m_vivoxLogoutPath, m_vivoxServer, m_authToken);
772  return VivoxCall(requrl, false);
773  }
774 
775 
776  private static readonly string m_vivoxGetAccountPath = "http://{0}/api2/viv_get_acct.php?auth_token={1}&user_name={2}";
777 
782  private XmlElement VivoxGetAccountInfo(string user)
783  {
784  string requrl = String.Format(m_vivoxGetAccountPath, m_vivoxServer, m_authToken, user);
785  return VivoxCall(requrl, true);
786  }
787 
788 
789  private static readonly string m_vivoxNewAccountPath = "http://{0}/api2/viv_adm_acct_new.php?username={1}&pwd={2}&auth_token={3}";
790 
797  private XmlElement VivoxCreateAccount(string user, string password)
798  {
799  string requrl = String.Format(m_vivoxNewAccountPath, m_vivoxServer, user, password, m_authToken);
800  return VivoxCall(requrl, true);
801  }
802 
803 
804  private static readonly string m_vivoxPasswordPath = "http://{0}/api2/viv_adm_password.php?user_name={1}&new_pwd={2}&auth_token={3}";
805 
809  private XmlElement VivoxPassword(string user, string password)
810  {
811  string requrl = String.Format(m_vivoxPasswordPath, m_vivoxServer, user, password, m_authToken);
812  return VivoxCall(requrl, true);
813  }
814 
815 
816  private static readonly string m_vivoxChannelPath = "http://{0}/api2/viv_chan_mod.php?mode={1}&chan_name={2}&auth_token={3}";
817 
830  private bool VivoxTryCreateChannel(string parent, string channelId, string description, out string channelUri)
831  {
832  string requrl = String.Format(m_vivoxChannelPath, m_vivoxServer, "create", channelId, m_authToken);
833 
834  if (!string.IsNullOrEmpty(parent))
835  {
836  requrl = String.Format("{0}&chan_parent={1}", requrl, parent);
837  }
838  if (!string.IsNullOrEmpty(description))
839  {
840  requrl = String.Format("{0}&chan_desc={1}", requrl, description);
841  }
842 
843  requrl = String.Format("{0}&chan_type={1}", requrl, m_vivoxChannelType);
844  requrl = String.Format("{0}&chan_mode={1}", requrl, m_vivoxChannelMode);
845  requrl = String.Format("{0}&chan_roll_off={1}", requrl, m_vivoxChannelRollOff);
846  requrl = String.Format("{0}&chan_dist_model={1}", requrl, m_vivoxChannelDistanceModel);
847  requrl = String.Format("{0}&chan_max_range={1}", requrl, m_vivoxChannelMaximumRange);
848  requrl = String.Format("{0}&chan_clamping_distance={1}", requrl, m_vivoxChannelClampingDistance);
849 
850  XmlElement resp = VivoxCall(requrl, true);
851  if (XmlFind(resp, "response.level0.body.chan_uri", out channelUri))
852  return true;
853 
854  channelUri = String.Empty;
855  return false;
856  }
857 
865  private bool VivoxTryCreateDirectory(string dirId, string description, out string channelId)
866  {
867  string requrl = String.Format(m_vivoxChannelPath, m_vivoxServer, "create", dirId, m_authToken);
868 
869  // if (parent != null && parent != String.Empty)
870  // {
871  // requrl = String.Format("{0}&chan_parent={1}", requrl, parent);
872  // }
873 
874  if (!string.IsNullOrEmpty(description))
875  {
876  requrl = String.Format("{0}&chan_desc={1}", requrl, description);
877  }
878  requrl = String.Format("{0}&chan_type={1}", requrl, "dir");
879 
880  XmlElement resp = VivoxCall(requrl, true);
881  if (IsOK(resp) && XmlFind(resp, "response.level0.body.chan_id", out channelId))
882  return true;
883 
884  channelId = String.Empty;
885  return false;
886  }
887 
888  private static readonly string m_vivoxChannelSearchPath = "http://{0}/api2/viv_chan_search.php?cond_channame={1}&auth_token={2}";
889 
901  private bool VivoxTryGetChannel(string channelParent, string channelName,
902  out string channelId, out string channelUri)
903  {
904  string count;
905 
906  string requrl = String.Format(m_vivoxChannelSearchPath, m_vivoxServer, channelName, m_authToken);
907  XmlElement resp = VivoxCall(requrl, true);
908 
909  if (XmlFind(resp, "response.level0.channel-search.count", out count))
910  {
911  int channels = Convert.ToInt32(count);
912 
913  // Bug in Vivox Server r2978 where count returns 0
914  // Found by Adam
915  if (channels == 0)
916  {
917  for (int j=0;j<100;j++)
918  {
919  string tmpId;
920  if (!XmlFind(resp, "response.level0.channel-search.channels.channels.level4.id", j, out tmpId))
921  break;
922 
923  channels = j + 1;
924  }
925  }
926 
927  for (int i = 0; i < channels; i++)
928  {
929  string name;
930  string id;
931  string type;
932  string uri;
933  string parent;
934 
935  // skip if not a channel
936  if (!XmlFind(resp, "response.level0.channel-search.channels.channels.level4.type", i, out type) ||
937  (type != "channel" && type != "positional_M"))
938  {
939  m_log.Debug("[VivoxVoice] Skipping Channel " + i + " as it's not a channel.");
940  continue;
941  }
942 
943  // skip if not the name we are looking for
944  if (!XmlFind(resp, "response.level0.channel-search.channels.channels.level4.name", i, out name) ||
945  name != channelName)
946  {
947  m_log.Debug("[VivoxVoice] Skipping Channel " + i + " as it has no name.");
948  continue;
949  }
950 
951  // skip if parent does not match
952  if (channelParent != null && !XmlFind(resp, "response.level0.channel-search.channels.channels.level4.parent", i, out parent))
953  {
954  m_log.Debug("[VivoxVoice] Skipping Channel " + i + "/" + name + " as it's parent doesnt match");
955  continue;
956  }
957 
958  // skip if no channel id available
959  if (!XmlFind(resp, "response.level0.channel-search.channels.channels.level4.id", i, out id))
960  {
961  m_log.Debug("[VivoxVoice] Skipping Channel " + i + "/" + name + " as it has no channel ID");
962  continue;
963  }
964 
965  // skip if no channel uri available
966  if (!XmlFind(resp, "response.level0.channel-search.channels.channels.level4.uri", i, out uri))
967  {
968  m_log.Debug("[VivoxVoice] Skipping Channel " + i + "/" + name + " as it has no channel URI");
969  continue;
970  }
971 
972  channelId = id;
973  channelUri = uri;
974 
975  return true;
976  }
977  }
978  else
979  {
980  m_log.Debug("[VivoxVoice] No count element?");
981  }
982 
983  channelId = String.Empty;
984  channelUri = String.Empty;
985 
986  // Useful incase something goes wrong.
987  //m_log.Debug("[VivoxVoice] Could not find channel in XMLRESP: " + resp.InnerXml);
988 
989  return false;
990  }
991 
992  private bool VivoxTryGetDirectory(string directoryName, out string directoryId)
993  {
994  string count;
995 
996  string requrl = String.Format(m_vivoxChannelSearchPath, m_vivoxServer, directoryName, m_authToken);
997  XmlElement resp = VivoxCall(requrl, true);
998 
999  if (XmlFind(resp, "response.level0.channel-search.count", out count))
1000  {
1001  int channels = Convert.ToInt32(count);
1002  for (int i = 0; i < channels; i++)
1003  {
1004  string name;
1005  string id;
1006  string type;
1007 
1008  // skip if not a directory
1009  if (!XmlFind(resp, "response.level0.channel-search.channels.channels.level4.type", i, out type) ||
1010  type != "dir")
1011  continue;
1012 
1013  // skip if not the name we are looking for
1014  if (!XmlFind(resp, "response.level0.channel-search.channels.channels.level4.name", i, out name) ||
1015  name != directoryName)
1016  continue;
1017 
1018  // skip if no channel id available
1019  if (!XmlFind(resp, "response.level0.channel-search.channels.channels.level4.id", i, out id))
1020  continue;
1021 
1022  directoryId = id;
1023  return true;
1024  }
1025  }
1026 
1027  directoryId = String.Empty;
1028  return false;
1029  }
1030 
1031  // private static readonly string m_vivoxChannelById = "https://{0}/api2/viv_chan_mod.php?mode={1}&chan_id={2}&auth_token={3}";
1032 
1033  // private XmlElement VivoxGetChannelById(string parent, string channelid)
1034  // {
1035  // string requrl = String.Format(m_vivoxChannelById, m_vivoxServer, "get", channelid, m_authToken);
1036 
1037  // if (parent != null && parent != String.Empty)
1038  // return VivoxGetChild(parent, channelid);
1039  // else
1040  // return VivoxCall(requrl, true);
1041  // }
1042 
1043  private static readonly string m_vivoxChannelDel = "http://{0}/api2/viv_chan_mod.php?mode={1}&chan_id={2}&auth_token={3}";
1044 
1056 
1057  private XmlElement VivoxDeleteChannel(string parent, string channelid)
1058  {
1059  string requrl = String.Format(m_vivoxChannelDel, m_vivoxServer, "delete", channelid, m_authToken);
1060  if (!string.IsNullOrEmpty(parent))
1061  {
1062  requrl = String.Format("{0}&chan_parent={1}", requrl, parent);
1063  }
1064  return VivoxCall(requrl, true);
1065  }
1066 
1067  private static readonly string m_vivoxChannelSearch = "http://{0}/api2/viv_chan_search.php?&cond_chanparent={1}&auth_token={2}";
1068 
1072 
1073  private XmlElement VivoxListChildren(string channelid)
1074  {
1075  string requrl = String.Format(m_vivoxChannelSearch, m_vivoxServer, channelid, m_authToken);
1076  return VivoxCall(requrl, true);
1077  }
1078 
1079  // private XmlElement VivoxGetChild(string parent, string child)
1080  // {
1081 
1082  // XmlElement children = VivoxListChildren(parent);
1083  // string count;
1084 
1085  // if (XmlFind(children, "response.level0.channel-search.count", out count))
1086  // {
1087  // int cnum = Convert.ToInt32(count);
1088  // for (int i = 0; i < cnum; i++)
1089  // {
1090  // string name;
1091  // string id;
1092  // if (XmlFind(children, "response.level0.channel-search.channels.channels.level4.name", i, out name))
1093  // {
1094  // if (name == child)
1095  // {
1096  // if (XmlFind(children, "response.level0.channel-search.channels.channels.level4.id", i, out id))
1097  // {
1098  // return VivoxGetChannelById(null, id);
1099  // }
1100  // }
1101  // }
1102  // }
1103  // }
1104 
1105  // // One we *know* does not exist.
1106  // return VivoxGetChannel(null, Guid.NewGuid().ToString());
1107 
1108  // }
1109 
1117  private XmlElement VivoxCall(string requrl, bool admin)
1118  {
1119 
1120  XmlDocument doc = null;
1121 
1122  // If this is an admin call, and admin is not connected,
1123  // and the admin id cannot be connected, then fail.
1124  if (admin && !m_adminConnected && !DoAdminLogin())
1125  return null;
1126 
1127  doc = new XmlDocument();
1128 
1129  // Let's serialize all calls to Vivox. Most of these are driven by
1130  // the clients (CAPs), when the user arrives at the region. We don't
1131  // want to issue many simultaneous http requests to Vivox, because mono
1132  // doesn't like that
1133  lock (m_Lock)
1134  {
1135  try
1136  {
1137  // Otherwise prepare the request
1138  //m_log.DebugFormat("[VivoxVoice] Sending request <{0}>", requrl);
1139 
1140  HttpWebRequest req = (HttpWebRequest)WebRequest.Create(requrl);
1141 
1142  // We are sending just parameters, no content
1143  req.ContentLength = 0;
1144 
1145  // Send request and retrieve the response
1146  using (HttpWebResponse rsp = (HttpWebResponse)req.GetResponse())
1147  using (Stream s = rsp.GetResponseStream())
1148  using (XmlTextReader rdr = new XmlTextReader(s))
1149  doc.Load(rdr);
1150  }
1151  catch (Exception e)
1152  {
1153  m_log.ErrorFormat("[VivoxVoice] Error in admin call : {0}", e.Message);
1154  }
1155  }
1156 
1157  // If we're debugging server responses, dump the whole
1158  // load now
1159  if (m_dumpXml) XmlScanl(doc.DocumentElement,0);
1160 
1161  return doc.DocumentElement;
1162  }
1163 
1167  private bool IsOK(XmlElement resp)
1168  {
1169  string status;
1170  XmlFind(resp, "response.level0.status", out status);
1171  return (status == "OK");
1172  }
1173 
1179  private bool DoAdminLogin()
1180  {
1181  m_log.Debug("[VivoxVoice] Establishing admin connection");
1182 
1183  lock (vlock)
1184  {
1185  if (!m_adminConnected)
1186  {
1187  string status = "Unknown";
1188  XmlElement resp = null;
1189 
1190  resp = VivoxLogin(m_vivoxAdminUser, m_vivoxAdminPassword);
1191 
1192  if (XmlFind(resp, "response.level0.body.status", out status))
1193  {
1194  if (status == "Ok")
1195  {
1196  m_log.Info("[VivoxVoice] Admin connection established");
1197  if (XmlFind(resp, "response.level0.body.auth_token", out m_authToken))
1198  {
1199  if (m_dumpXml) m_log.DebugFormat("[VivoxVoice] Auth Token <{0}>",
1200  m_authToken);
1201  m_adminConnected = true;
1202  }
1203  }
1204  else
1205  {
1206  m_log.WarnFormat("[VivoxVoice] Admin connection failed, status = {0}",
1207  status);
1208  }
1209  }
1210  }
1211  }
1212 
1213  return m_adminConnected;
1214  }
1215 
1223  private void XmlScanl(XmlElement e, int index)
1224  {
1225  if (e.HasChildNodes)
1226  {
1227  m_log.DebugFormat("<{0}>".PadLeft(index+5), e.Name);
1228  XmlNodeList children = e.ChildNodes;
1229  foreach (XmlNode node in children)
1230  switch (node.NodeType)
1231  {
1232  case XmlNodeType.Element :
1233  XmlScanl((XmlElement)node, index+1);
1234  break;
1235  case XmlNodeType.Text :
1236  m_log.DebugFormat("\"{0}\"".PadLeft(index+5), node.Value);
1237  break;
1238  default :
1239  break;
1240  }
1241  m_log.DebugFormat("</{0}>".PadLeft(index+6), e.Name);
1242  }
1243  else
1244  {
1245  m_log.DebugFormat("<{0}/>".PadLeft(index+6), e.Name);
1246  }
1247  }
1248 
1249  private static readonly char[] C_POINT = {'.'};
1250 
1262  private bool XmlFind(XmlElement root, string tag, int nth, out string result)
1263  {
1264  if (root == null || tag == null || tag == String.Empty)
1265  {
1266  result = String.Empty;
1267  return false;
1268  }
1269  return XmlSearch(root,tag.Split(C_POINT),0, ref nth, out result);
1270  }
1271 
1272  private bool XmlFind(XmlElement root, string tag, out string result)
1273  {
1274  int nth = 0;
1275  if (root == null || tag == null || tag == String.Empty)
1276  {
1277  result = String.Empty;
1278  return false;
1279  }
1280  return XmlSearch(root,tag.Split(C_POINT),0, ref nth, out result);
1281  }
1282 
1294  private bool XmlSearch(XmlElement e, string[] tags, int index, ref int nth, out string result)
1295  {
1296  if (index == tags.Length || e.Name != tags[index])
1297  {
1298  result = String.Empty;
1299  return false;
1300  }
1301 
1302  if (tags.Length-index == 1)
1303  {
1304  if (nth == 0)
1305  {
1306  result = e.InnerText;
1307  return true;
1308  }
1309  else
1310  {
1311  nth--;
1312  result = String.Empty;
1313  return false;
1314  }
1315  }
1316 
1317  if (e.HasChildNodes)
1318  {
1319  XmlNodeList children = e.ChildNodes;
1320  foreach (XmlNode node in children)
1321  {
1322  switch (node.NodeType)
1323  {
1324  case XmlNodeType.Element :
1325  if (XmlSearch((XmlElement)node, tags, index+1, ref nth, out result))
1326  return true;
1327  break;
1328 
1329  default :
1330  break;
1331  }
1332  }
1333  }
1334 
1335  result = String.Empty;
1336  return false;
1337  }
1338 
1339  private void HandleDebug(string module, string[] cmd)
1340  {
1341  if (cmd.Length < 3)
1342  {
1343  MainConsole.Instance.Output("Error: missing on/off flag");
1344  return;
1345  }
1346 
1347  if (cmd[2] == "on")
1348  m_dumpXml = true;
1349  else if (cmd[2] == "off")
1350  m_dumpXml = false;
1351  else
1352  MainConsole.Instance.Output("Error: only on and off are supported");
1353  }
1354  }
1355 }
EstateSettings EstateSettings
Definition: RegionInfo.cs:275
string ParcelVoiceInfoRequest(Scene scene, string request, string path, string param, UUID agentID, Caps caps)
Callback for a client request for ParcelVoiceInfo
void Close()
This is the inverse to Initialise. After a Close(), this instance won't be usable anymore...
string ChatSessionRequest(Scene scene, string request, string path, string param, UUID agentID, Caps caps)
Callback for a client request for a private chat channel
void Initialise(IConfigSource config)
This is called to initialize the region module. For shared modules, this is called exactly once...
OpenSim.Framework.Capabilities.Caps Caps
void AddRegion(Scene scene)
This is called whenever a Scene is added. For shared modules, this can happen several times...
Details of a Parcel of land
Definition: LandData.cs:47
void RegionLoaded(Scene scene)
This will be called once for every scene loaded. In a shared module this will be multiple times in on...
Interactive OpenSim region server
Definition: OpenSim.cs:55
void PostInitialise()
This is called exactly once after all the shared region-modules have been instanciated and IRegionMod...
string ProvisionVoiceAccountRequest(Scene scene, string request, string path, string param, UUID agentID, Caps caps)
Callback for a client request for Voice Account Details
void RemoveRegion(Scene scene)
This is called whenever a Scene is removed. For shared modules, this can happen several times...