diff --git a/OpenSim/Addons/os-webrtc-janus/Janus/JanusMessages.cs b/OpenSim/Addons/os-webrtc-janus/Janus/JanusMessages.cs index 8daefafc67..f1b1e5754b 100644 --- a/OpenSim/Addons/os-webrtc-janus/Janus/JanusMessages.cs +++ b/OpenSim/Addons/os-webrtc-janus/Janus/JanusMessages.cs @@ -95,7 +95,7 @@ namespace osWebRtcVoice } // Note that the session_id is a long number in the JSON so we convert the string. public string sessionId { - get { return m_message.ContainsKey("session_id") ? OSDToLong(m_message["session_id"]).ToString() : String.Empty; } + get { return m_message.TryGetValue("session_id", out OSD tmposd) ? OSDToLong(tmposd).ToString() : string.Empty; } set { m_message["session_id"] = long.Parse(value); } } public bool hasSessionId { get { return m_message.ContainsKey("session_id"); } } @@ -134,7 +134,7 @@ namespace osWebRtcVoice // and one fetches it with .AsInteger(), it will return the first 4 bytes as an integer // and not the long value. So this function looks at the type of the OSD object and // extracts the number appropriately. - public long OSDToLong(OSD pIn) + public static long OSDToLong(OSD pIn) { long ret = 0; switch (pIn.Type) @@ -210,7 +210,7 @@ namespace osWebRtcVoice } // Return the "data" portion of the response as an OSDMap or null if there is none - public OSDMap dataSection { get { return m_message.ContainsKey("data") ? (m_message["data"] as OSDMap) : null; } } + public OSDMap dataSection { get { return m_message.TryGetOSDMap("data", out OSDMap osdm) ? osdm : null; } } // Check if a successful response code is in the response public virtual bool isSuccess { get { return CheckReturnCode("success"); } } diff --git a/OpenSim/Addons/os-webrtc-janus/WebRtcVoice/WebRtcVoiceServiceConnector.cs b/OpenSim/Addons/os-webrtc-janus/WebRtcVoice/WebRtcVoiceServiceConnector.cs index cb134d3059..41b68d798e 100644 --- a/OpenSim/Addons/os-webrtc-janus/WebRtcVoice/WebRtcVoiceServiceConnector.cs +++ b/OpenSim/Addons/os-webrtc-janus/WebRtcVoice/WebRtcVoiceServiceConnector.cs @@ -76,7 +76,6 @@ namespace osWebRtcVoice m_MessageDetails = moduleConfig.GetBoolean("MessageDetails", false); } } - } // Create a local viewer session. This gets a local viewer session ID that is diff --git a/OpenSim/Addons/os-webrtc-janus/WebRtcVoiceRegionModule/WebRtcVoiceRegionModule.cs b/OpenSim/Addons/os-webrtc-janus/WebRtcVoiceRegionModule/WebRtcVoiceRegionModule.cs index 2f4b7581fc..22e05c2a43 100644 --- a/OpenSim/Addons/os-webrtc-janus/WebRtcVoiceRegionModule/WebRtcVoiceRegionModule.cs +++ b/OpenSim/Addons/os-webrtc-janus/WebRtcVoiceRegionModule/WebRtcVoiceRegionModule.cs @@ -56,7 +56,7 @@ namespace osWebRtcVoice /// This module provides the WebRTC voice interface for viewer clients.. /// /// In particular, it provides the following capabilities: - /// ProvisionVoiceAccountRequest, VoiceSignalingRequest, and ParcelVoiceInfoRequest. + /// ProvisionVoiceAccountRequest, VoiceSignalingRequest and limited ChatSessionRequest /// which are the user interface to the voice service. /// /// Initially, when the user connects to the region, the region feature "VoiceServiceType" is @@ -70,14 +70,12 @@ namespace osWebRtcVoice private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private static readonly string logHeader = "[REGION WEBRTC VOICE]"; + private static byte[] llsdUndefAnswerBytes = Util.UTF8.GetBytes(""); private bool _MessageDetails = false; // Control info private static bool m_Enabled = false; - private readonly Dictionary m_UUIDName = new(); - private Dictionary m_ParcelAddress = new(); - private IConfig m_Config; // ISharedRegionModule.Initialize @@ -149,7 +147,7 @@ namespace osWebRtcVoice // everytime OpenSim hands out capabilities to a client // (login, region crossing). We contribute three capabilities to // the set of capabilities handed back to the client: - // ProvisionVoiceAccountRequest, VoiceSignalingRequest, and ParcelVoiceInfoRequest. + // ProvisionVoiceAccountRequest, VoiceSignalingRequest and limited ChatSessionRequest // // ProvisionVoiceAccountRequest allows the client to obtain // voice communication information the the avater. @@ -165,7 +163,7 @@ namespace osWebRtcVoice public void OnRegisterCaps(Scene scene, UUID agentID, Caps caps) { m_log.Debug( - $"{logHeader}: OnRegisterCaps() called with agentID {agentID} caps {caps} in scene {scene.RegionInfo.RegionName}"); + $"{logHeader}: OnRegisterCaps called with agentID {agentID} caps {caps} in scene {scene.Name}"); caps.RegisterSimpleHandler("ProvisionVoiceAccountRequest", new SimpleStreamHandler("/" + UUID.Random(), (IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) => @@ -202,7 +200,7 @@ namespace osWebRtcVoice IWebRtcVoiceService voiceService = scene.RequestModuleInterface(); if (voiceService is null) { - m_log.Error($"{logHeader}[ProvisionVoice]: no voice service not loaded"); + m_log.Error($"{logHeader}[ProvisionVoice]: voice service not loaded"); response.StatusCode = (int)HttpStatusCode.NotFound; return; } @@ -229,7 +227,7 @@ namespace osWebRtcVoice if (vstosd is OSDString vst && !((string)vst).Equals("webrtc", StringComparison.OrdinalIgnoreCase)) { m_log.Warn($"{logHeader}[ProvisionVoice]: voice_server_type is not 'webrtc'. Request: {map}"); - response.RawBuffer = Util.UTF8.GetBytes(""); + response.RawBuffer = llsdUndefAnswerBytes; response.StatusCode = (int)HttpStatusCode.OK; return; } @@ -237,6 +235,76 @@ namespace osWebRtcVoice if (_MessageDetails) m_log.DebugFormat($"{logHeader}[ProvisionVoice]: request: {map}"); + if (map.TryGetString("channel_type", out string channelType)) + { + //do fully not trust viewers voice parcel requests + if (channelType == "local") + { + if (!scene.RegionInfo.EstateSettings.AllowVoice) + { + m_log.Debug($"{logHeader}[ProvisionVoice]:region \"{scene.Name}\": voice not enabled in estate settings"); + response.RawBuffer = llsdUndefAnswerBytes; + response.StatusCode = (int)HttpStatusCode.NotImplemented; + return; + } + if (scene.LandChannel == null) + { + m_log.Error($"{logHeader}[ProvisionVoice] region \"{scene.Name}\" land data not yet available"); + response.RawBuffer = llsdUndefAnswerBytes; + response.StatusCode = (int)HttpStatusCode.NotImplemented; + return; + } + + if(!scene.TryGetScenePresence(agentID, out ScenePresence sp)) + { + m_log.Debug($"{logHeader}[ProvisionVoice]:avatar not found"); + response.RawBuffer = llsdUndefAnswerBytes; + response.StatusCode = (int)HttpStatusCode.NotFound; + return; + } + + if(map.TryGetInt("parcel_local_id", out int parcelID)) + { + ILandObject parcel = scene.LandChannel.GetLandObject(parcelID); + if (parcel == null) + { + response.RawBuffer = llsdUndefAnswerBytes; + response.StatusCode = (int)HttpStatusCode.NotFound; + return; + } + + LandData land = parcel.LandData; + if (land == null) + { + response.RawBuffer = llsdUndefAnswerBytes; + response.StatusCode = (int)HttpStatusCode.NotFound; + return; + } + + if (!scene.RegionInfo.EstateSettings.TaxFree && (land.Flags & (uint)ParcelFlags.AllowVoiceChat) == 0) + { + m_log.Debug($"{logHeader}[ProvisionVoice]:parcel voice not allowed"); + response.RawBuffer = llsdUndefAnswerBytes; + response.StatusCode = (int)HttpStatusCode.Forbidden; + return; + } + + if ((land.Flags & (uint)ParcelFlags.UseEstateVoiceChan) != 0) + { + map.Remove("parcel_local_id"); // estate channel + } + else if(parcel.IsRestrictedFromLand(agentID) || parcel.IsBannedFromLand(agentID)) + { + // check Z distance? + m_log.Debug($"{logHeader}[ProvisionVoice]:agent not allowed on parcel"); + response.RawBuffer = llsdUndefAnswerBytes; + response.StatusCode = (int)HttpStatusCode.Forbidden; + return; + } + } + } + } + // The checks passed. Send the request to the voice service. OSDMap resp = voiceService.ProvisionVoiceAccountRequest(map, agentID, scene.RegionInfo.RegionID).Result; @@ -290,18 +358,20 @@ namespace osWebRtcVoice { if (vstosd is OSDString vst && !((string)vst).Equals("webrtc", StringComparison.OrdinalIgnoreCase)) { - response.RawBuffer = Util.UTF8.GetBytes(""); + response.RawBuffer = llsdUndefAnswerBytes; + response.StatusCode = (int)HttpStatusCode.OK; return; } } OSDMap resp = voiceService.VoiceSignalingRequest(map, agentID, scene.RegionInfo.RegionID).Result; + if (_MessageDetails) m_log.Debug($"{logHeader}[VoiceSignalingRequest]: Response: {resp}"); // TODO: check for errors and package the response + response.RawBuffer = llsdUndefAnswerBytes; response.StatusCode = (int)HttpStatusCode.OK; - response.RawBuffer = Util.UTF8.GetBytes(""); return; } @@ -401,161 +471,6 @@ namespace osWebRtcVoice } } - // NOTE NOTE!! This is code from the FreeSwitch module. It is not clear if this is correct for WebRtc. - /// - /// Callback for a client request for ParcelVoiceInfo - /// - /// current scene object of the client - /// - /// - /// - /// - /// - /// - public void ParcelVoiceInfoRequest(IOSHttpRequest request, IOSHttpResponse response, UUID agentID, Scene scene) - { - if (request.HttpMethod != "POST") - { - response.StatusCode = (int)HttpStatusCode.NotFound; - return; - } - - response.StatusCode = (int)HttpStatusCode.OK; - - m_log.DebugFormat( - "{0}[PARCELVOICE]: ParcelVoiceInfoRequest() on {1} for {2}", - logHeader, scene.RegionInfo.RegionName, agentID); - - ScenePresence avatar = scene.GetScenePresence(agentID); - if (avatar == null) - { - response.RawBuffer = Util.UTF8.GetBytes("undef"); - return; - } - - string avatarName = avatar.Name; - - // - check whether we have a region channel in our cache - // - if not: - // create it and cache it - // - send it to the client - // - send channel_uri: as "sip:regionID@m_sipDomain" - try - { - string channelUri; - - if (null == scene.LandChannel) - { - m_log.ErrorFormat("region \"{0}\": avatar \"{1}\": land data not yet available", - scene.RegionInfo.RegionName, avatarName); - response.RawBuffer = Util.UTF8.GetBytes("undef"); - return; - } - - // get channel_uri: check first whether estate - // settings allow voice, then whether parcel allows - // voice, if all do retrieve or obtain the parcel - // voice channel - LandData land = scene.GetLandData(avatar.AbsolutePosition); - - // TODO: EstateSettings don't seem to get propagated... - if (!scene.RegionInfo.EstateSettings.AllowVoice) - { - m_log.DebugFormat("{0}[PARCELVOICE]: region \"{1}\": voice not enabled in estate settings", - logHeader, scene.RegionInfo.RegionName); - channelUri = String.Empty; - } - else - - if (!scene.RegionInfo.EstateSettings.TaxFree && (land.Flags & (uint)ParcelFlags.AllowVoiceChat) == 0) - { - channelUri = String.Empty; - } - else - { - channelUri = ChannelUri(scene, land); - } - - // fast foward encode - osUTF8 lsl = LLSDxmlEncode2.Start(512); - LLSDxmlEncode2.AddMap(lsl); - LLSDxmlEncode2.AddElem("parcel_local_id", land.LocalID, lsl); - LLSDxmlEncode2.AddElem("region_name", scene.Name, lsl); - LLSDxmlEncode2.AddMap("voice_credentials", lsl); - LLSDxmlEncode2.AddElem("channel_uri", channelUri, lsl); - //LLSDxmlEncode2.AddElem("channel_credentials", channel_credentials, lsl); - LLSDxmlEncode2.AddEndMap(lsl); - LLSDxmlEncode2.AddEndMap(lsl); - - response.RawBuffer = LLSDxmlEncode2.EndToBytes(lsl); - } - catch (Exception e) - { - m_log.ErrorFormat("{0}[PARCELVOICE]: region \"{1}\": avatar \"{2}\": {3}, retry later", - logHeader, scene.RegionInfo.RegionName, avatarName, e.Message); - m_log.DebugFormat("{0}[PARCELVOICE]: region \"{1}\": avatar \"{2}\": {3} failed", - logHeader, scene.RegionInfo.RegionName, avatarName, e.ToString()); - - response.RawBuffer = Util.UTF8.GetBytes("undef"); - } - } - - // NOTE NOTE!! This is code from the FreeSwitch module. It is not clear if this is correct for WebRtc. - // Not sure what this Uri is for. Is this FreeSwitch specific? - // TODO: is this useful for WebRtc? - private string ChannelUri(Scene scene, LandData land) - { - string channelUri = null; - - string landUUID; - string landName; - - // Create parcel voice channel. If no parcel exists, then the voice channel ID is the same - // as the directory ID. Otherwise, it reflects the parcel's ID. - - lock (m_ParcelAddress) - { - if (m_ParcelAddress.ContainsKey(land.GlobalID.ToString())) - { - m_log.DebugFormat("{0}: parcel id {1}: using sip address {2}", - logHeader, land.GlobalID, m_ParcelAddress[land.GlobalID.ToString()]); - return m_ParcelAddress[land.GlobalID.ToString()]; - } - } - - if (land.LocalID != 1 && (land.Flags & (uint)ParcelFlags.UseEstateVoiceChan) == 0) - { - landName = String.Format("{0}:{1}", scene.RegionInfo.RegionName, land.Name); - landUUID = land.GlobalID.ToString(); - m_log.DebugFormat("{0}: Region:Parcel \"{1}\": parcel id {2}: using channel name {3}", - logHeader, landName, land.LocalID, landUUID); - } - else - { - landName = String.Format("{0}:{1}", scene.RegionInfo.RegionName, scene.RegionInfo.RegionName); - landUUID = scene.RegionInfo.RegionID.ToString(); - m_log.DebugFormat("{0}: Region:Parcel \"{1}\": parcel id {2}: using channel name {3}", - logHeader, landName, land.LocalID, landUUID); - } - - // slvoice handles the sip address differently if it begins with confctl, hiding it from the user in - // the friends list. however it also disables the personal speech indicators as well unless some - // siren14-3d codec magic happens. we dont have siren143d so we'll settle for the personal speech indicator. - channelUri = String.Format("sip:conf-{0}@{1}", - "x" + Convert.ToBase64String(Encoding.ASCII.GetBytes(landUUID)), - /*m_freeSwitchRealm*/ "webRTC"); - - lock (m_ParcelAddress) - { - if (!m_ParcelAddress.ContainsKey(land.GlobalID.ToString())) - { - m_ParcelAddress.Add(land.GlobalID.ToString(), channelUri); - } - } - - return channelUri; - } - /// /// Convert the LLSDXml body of the request to an OSDMap for easier handling. /// Also logs the request if message details is enabled. diff --git a/OpenSim/Addons/os-webrtc-janus/WebRtcVoiceServiceModule/WebRtcVoiceServiceModule.cs b/OpenSim/Addons/os-webrtc-janus/WebRtcVoiceServiceModule/WebRtcVoiceServiceModule.cs index dbad8e0952..91c215129e 100644 --- a/OpenSim/Addons/os-webrtc-janus/WebRtcVoiceServiceModule/WebRtcVoiceServiceModule.cs +++ b/OpenSim/Addons/os-webrtc-janus/WebRtcVoiceServiceModule/WebRtcVoiceServiceModule.cs @@ -26,12 +26,10 @@ */ using System; -using System.Linq; using System.Collections.Generic; using System.Reflection; using System.Threading.Tasks; -using OpenSim.Framework; using OpenSim.Region.Framework.Scenes; using OpenSim.Region.Framework.Interfaces; using OpenSim.Server.Base; @@ -85,49 +83,46 @@ namespace osWebRtcVoice if (m_Enabled) { // Get the DLLs for the two voice services - string spatialDllName = moduleConfig.GetString("SpatialVoiceService", String.Empty); - string nonSpatialDllName = moduleConfig.GetString("NonSpatialVoiceService", String.Empty); - if (String.IsNullOrEmpty(spatialDllName) && String.IsNullOrEmpty(nonSpatialDllName)) + string spatialDllName = moduleConfig.GetString("SpatialVoiceService", string.Empty); + string nonSpatialDllName = moduleConfig.GetString("NonSpatialVoiceService", string.Empty); + if (string.IsNullOrEmpty(spatialDllName) && string.IsNullOrEmpty(nonSpatialDllName)) { - m_log.ErrorFormat("{0} No SpatialVoiceService or NonSpatialVoiceService specified in configuration", LogHeader); + m_log.Error($"{LogHeader} No VoiceService specified in configuration"); m_Enabled = false; + return; } // Default non-spatial to spatial if not specified - if (String.IsNullOrEmpty(nonSpatialDllName)) + if (string.IsNullOrEmpty(nonSpatialDllName)) { - m_log.DebugFormat("{0} nonSpatialDllName not specified. Defaulting to spatialDllName", LogHeader); + m_log.Debug($"{LogHeader} nonSpatialDllName not specified. Defaulting to spatialDllName"); nonSpatialDllName = spatialDllName; } // Load the two voice services - m_log.DebugFormat("{0} Loading SpatialVoiceService from {1}", LogHeader, spatialDllName); - m_spatialVoiceService = ServerUtils.LoadPlugin(spatialDllName, new object[] { m_Config }); + m_log.Debug($"{LogHeader} Loading SpatialVoiceService from {spatialDllName}"); + m_spatialVoiceService = ServerUtils.LoadPlugin(spatialDllName, [m_Config]); if (m_spatialVoiceService is null) { - m_log.ErrorFormat("{0} Could not load SpatialVoiceService from {1}", LogHeader, spatialDllName); + m_log.Error($"{LogHeader} Could not load SpatialVoiceService from {spatialDllName}, module disabled"); m_Enabled = false; + return; } - m_log.DebugFormat("{0} Loading NonSpatialVoiceService from {1}", LogHeader, nonSpatialDllName); - if (spatialDllName == nonSpatialDllName) + m_log.Debug($"{LogHeader} Loading NonSpatialVoiceService from {nonSpatialDllName}"); + if (spatialDllName != nonSpatialDllName) { - m_log.DebugFormat("{0} NonSpatialVoiceService is same as SpatialVoiceService", LogHeader); - m_nonSpatialVoiceService = m_spatialVoiceService; - } - else - { - m_nonSpatialVoiceService = ServerUtils.LoadPlugin(nonSpatialDllName, new object[] { m_Config }); + m_nonSpatialVoiceService = ServerUtils.LoadPlugin(nonSpatialDllName, [ m_Config ]); if (m_nonSpatialVoiceService is null) { - m_log.ErrorFormat("{0} Could not load NonSpatialVoiceService from {1}", LogHeader, nonSpatialDllName); + m_log.Error("{LogHeader} Could not load NonSpatialVoiceService from {nonSpatialDllName}"); m_Enabled = false; } } if (m_Enabled) { - m_log.InfoFormat("{0} WebRtcVoiceService enabled", LogHeader); + m_log.Info($"{LogHeader} WebRtcVoiceService enabled"); } } } @@ -160,7 +155,7 @@ namespace osWebRtcVoice { if (m_Enabled) { - m_log.DebugFormat("{0} Adding WebRtcVoiceService to region {1}", LogHeader, scene.Name); + m_log.Debug($"{LogHeader} Adding WebRtcVoiceService to region {scene.Name}"); scene.RegisterModuleInterface(this); // TODO: figure out what events we care about @@ -218,21 +213,19 @@ namespace osWebRtcVoice { OSDMap response = null; IVoiceViewerSession vSession = null; - if (pRequest.ContainsKey("viewer_session")) + if (pRequest.TryGetString("viewer_session", out string viewerSessionId)) { // request has a viewer session. Use that to find the voice service - string viewerSessionId = pRequest["viewer_session"].AsString(); if (!VoiceViewerSession.TryGetViewerSession(viewerSessionId, out vSession)) { - m_log.ErrorFormat("{0} ProvisionVoiceAccountRequest: viewer session {1} not found", LogHeader, viewerSessionId); + m_log.Error($"{0} ProvisionVoiceAccountRequest: viewer session {viewerSessionId} not found"); } } else { // the request does not have a viewer session. See if it's an initial request - if (pRequest.ContainsKey("channel_type")) + if (pRequest.TryGetString("channel_type", out string channelType)) { - string channelType = pRequest["channel_type"].AsString(); if (channelType == "local") { // TODO: check if this userId is making a new session (case that user is reconnecting) @@ -248,7 +241,7 @@ namespace osWebRtcVoice } else { - m_log.ErrorFormat("{0} ProvisionVoiceAccountRequest: no channel_type in request", LogHeader); + m_log.Error($"{LogHeader} ProvisionVoiceAccountRequest: no channel_type in request"); } } if (vSession is not null) @@ -263,10 +256,9 @@ namespace osWebRtcVoice { OSDMap response = null; IVoiceViewerSession vSession = null; - if (pRequest.ContainsKey("viewer_session")) + if (pRequest.TryGetString("viewer_session", out string viewerSessionId)) { // request has a viewer session. Use that to find the voice service - string viewerSessionId = pRequest["viewer_session"].AsString(); if (VoiceViewerSession.TryGetViewerSession(viewerSessionId, out vSession)) { response = await vSession.VoiceService.VoiceSignalingRequest(vSession, pRequest, pUserID, pSceneID);