diff --git a/OpenSim/Addons/os-webrtc-janus/Janus/JanusAudioBridge.cs b/OpenSim/Addons/os-webrtc-janus/Janus/JanusAudioBridge.cs index b6186b55ab..3052681c45 100644 --- a/OpenSim/Addons/os-webrtc-janus/Janus/JanusAudioBridge.cs +++ b/OpenSim/Addons/os-webrtc-janus/Janus/JanusAudioBridge.cs @@ -84,12 +84,12 @@ namespace osWebRtcVoice /// boolean on whether room will be spatial or non-spatial /// added as "description" to the created room /// - public async Task CreateRoom(int pRoomId, bool pSpatial, string pRoomDesc) + public async Task CreateRoom(int pRoomId, bool pSpatial, string pRoomDesc, string credentials) { JanusRoom ret = null; try { - JanusMessageResp resp = await SendPluginMsg(new AudioBridgeCreateRoomReq(pRoomId, pSpatial, pRoomDesc)).ConfigureAwait(false); + JanusMessageResp resp = await SendPluginMsg(new AudioBridgeCreateRoomReq(pRoomId, pSpatial, pRoomDesc, credentials)).ConfigureAwait(false); AudioBridgeResp abResp = new(resp); m_log.Debug($"{LogHeader} CreateRoom. ReturnCode: '{abResp.AudioBridgeReturnCode}'"); @@ -156,7 +156,7 @@ namespace osWebRtcVoice // The attempt is to deterministicly create a room number so all regions will generate the // same room number across sessions and across the grid. // getHashCode() is not deterministic across sessions. - public static int CalcRoomNumber(string pRegionId, string pChannelType, int pParcelLocalID, string pChannelID) + public static int CalcRoomNumber(string regionID, string gridhash, string pChannelType, int pParcelLocalID, string pChannelID) { BHasherMdjb2 hasher = new(); // If there is a channel specified it must be group @@ -164,13 +164,12 @@ namespace osWebRtcVoice { case "local": // A "local" channel is unique to the region and parcel - hasher.Add(pRegionId); + hasher.Add(regionID); hasher.Add(pChannelType); hasher.Add(pParcelLocalID); break; case "multiagent": - // A "multiagent" channel is unique to the grid - // should add a GridId here + hasher.Add(gridhash); hasher.Add(pChannelID); hasher.Add(pChannelType); break; @@ -184,61 +183,61 @@ namespace osWebRtcVoice return roomNumber; } - public async Task SelectRoom(string pRegionId, string pChannelType, bool pSpatial, int pParcelLocalID, string pChannelID) + public async Task SelectRoom(string pRegionId, string pgridhash, string pChannelType, bool pSpatial, int pParcelLocalID, string pChannelID, string credentials) { - int roomNumber = CalcRoomNumber(pRegionId, pChannelType, pParcelLocalID, pChannelID); + int roomNumber = CalcRoomNumber(pRegionId, pgridhash, pChannelType, pParcelLocalID, pChannelID); // Should be unique for the given use and channel type - m_log.DebugFormat("{0} SelectRoom: roomNumber={1}", LogHeader, roomNumber); + m_log.Debug($"{LogHeader} SelectRoom: roomNumber={roomNumber}"); // Check to see if the room has already been created + JanusRoom existingRoom; lock (_rooms) { - if (_rooms.ContainsKey(roomNumber)) + if (_rooms.TryGetValue(roomNumber, out existingRoom)) { - return _rooms[roomNumber]; + return existingRoom; } } // The room doesn't exist. Create it. - string roomDesc = pRegionId + "/" + pChannelType + "/" + pParcelLocalID + "/" + pChannelID; - JanusRoom ret = await CreateRoom(roomNumber, pSpatial, roomDesc).ConfigureAwait(false); + string roomDesc; + if(pChannelType == "local") + roomDesc = $"{pRegionId}/{pChannelType}/{pParcelLocalID}"; + else + roomDesc = $"{pgridhash}/{pChannelType}/{pChannelID}"; + + JanusRoom ret = await CreateRoom(roomNumber, pSpatial, roomDesc, credentials).ConfigureAwait(false); - JanusRoom existingRoom = null; if (ret is not null) { lock (_rooms) { - if (_rooms.ContainsKey(roomNumber)) + if(!_rooms.TryGetValue(roomNumber, out existingRoom)) { // If the room was created while we were waiting, - existingRoom = _rooms[roomNumber]; - } - else - { - // Our room is the first one created. Save it. _rooms[roomNumber] = ret; } } } + if (existingRoom is not null) { // The room we created was already created by someone else. Delete ours and use the existing one - await DestroyRoom(ret); - ret = existingRoom; + await DestroyRoom(ret).ConfigureAwait(false); + return existingRoom; } + return ret; } // Return the room with the given room ID or 'null' if no such room - public JanusRoom GetRoom(int pRoomId) + public bool GetRoom(int pRoomId, out JanusRoom room) { - JanusRoom ret = null; lock (_rooms) { - _rooms.TryGetValue(pRoomId, out ret); + return _rooms.TryGetValue(pRoomId, out room); } - return ret; } public override void Handle_Event(JanusMessageResp pResp) @@ -250,8 +249,8 @@ namespace osWebRtcVoice // An audio bridge event! m_log.DebugFormat("{0} Handle_Event. {1}", LogHeader, abResp.ToString()); } - } + public override void Handle_Message(JanusMessageResp pResp) { base.Handle_Message(pResp); @@ -261,7 +260,6 @@ namespace osWebRtcVoice // An audio bridge event! m_log.DebugFormat("{0} Handle_Event. {1}", LogHeader, abResp.ToString()); } - } } } diff --git a/OpenSim/Addons/os-webrtc-janus/Janus/JanusMessages.cs b/OpenSim/Addons/os-webrtc-janus/Janus/JanusMessages.cs index 44f4378d56..195f4d6080 100644 --- a/OpenSim/Addons/os-webrtc-janus/Janus/JanusMessages.cs +++ b/OpenSim/Addons/os-webrtc-janus/Janus/JanusMessages.cs @@ -514,11 +514,11 @@ namespace osWebRtcVoice // ============================================================== public class AudioBridgeCreateRoomReq : PluginMsgReq { - public AudioBridgeCreateRoomReq(int pRoomId) : this(pRoomId, false, null) + public AudioBridgeCreateRoomReq(int pRoomId) : this(pRoomId, false, null, null) { } - public AudioBridgeCreateRoomReq(int pRoomId, bool pSpatial, string pDesc) : base(new OSDMap() { + public AudioBridgeCreateRoomReq(int pRoomId, bool pSpatial, string pDesc, string credentials) : base(new OSDMap() { { "room", pRoomId }, { "request", "create" }, { "is_private", false }, @@ -531,6 +531,8 @@ namespace osWebRtcVoice { if (!string.IsNullOrEmpty(pDesc)) AddStringToBody("description", pDesc); + if (!string.IsNullOrEmpty(credentials)) + AddStringToBody("pin", credentials); } } diff --git a/OpenSim/Addons/os-webrtc-janus/Janus/JanusRoom.cs b/OpenSim/Addons/os-webrtc-janus/Janus/JanusRoom.cs index a49911fb4e..99ae57456e 100644 --- a/OpenSim/Addons/os-webrtc-janus/Janus/JanusRoom.cs +++ b/OpenSim/Addons/os-webrtc-janus/Janus/JanusRoom.cs @@ -59,7 +59,6 @@ namespace osWebRtcVoice public async Task JoinRoom(JanusViewerSession pVSession) { - bool ret = false; try { // m_log.DebugFormat("{0} JoinRoom. New joinReq for room {1}", LogHeader, RoomId); @@ -79,10 +78,11 @@ namespace osWebRtcVoice { pVSession.ParticipantId = joinResp.ParticipantId; pVSession.Answer = joinResp.Jsep; - ret = true; m_log.Debug($"{LogHeader} JoinRoom. Joined room {RoomId}. Participant={pVSession.ParticipantId}"); + return true; } - else if (joinResp is not null && joinResp.AudioBridgeErrorCode == 491) + + if (joinResp is not null && (joinResp.AudioBridgeErrorCode == 490 || joinResp.AudioBridgeErrorCode == 491)) { m_log.Warn($"{LogHeader} JoinRoom. Already in a room for agent {pVSession.AgentId}. Attempting recovery."); @@ -98,13 +98,11 @@ namespace osWebRtcVoice { pVSession.ParticipantId = retryJoinResp.ParticipantId; pVSession.Answer = retryJoinResp.Jsep; - ret = true; m_log.Info($"{LogHeader} JoinRoom. Recovery succeeded for room {RoomId}. Participant={pVSession.ParticipantId}"); + return true; } - else - { - m_log.Error($"{LogHeader} JoinRoom. Recovery retry failed for room {RoomId}. Resp={retryJoinResp?.ToString() ?? "null"}"); - } + + m_log.Error($"{LogHeader} JoinRoom. Recovery retry failed for room {RoomId}. Resp={retryJoinResp?.ToString() ?? "null"}"); } else { @@ -120,18 +118,19 @@ namespace osWebRtcVoice if (m_log.IsDebugEnabled) m_log.DebugFormat("{0} JoinRoom. Invalid participant detail: {1}", LogHeader, joinResp.ToString()); } - - m_log.ErrorFormat("{0} JoinRoom. Failed to join room {1}", LogHeader, RoomId); - if (m_log.IsDebugEnabled) - m_log.DebugFormat("{0} JoinRoom. Failure detail: {1}", LogHeader, joinResp?.ToString() ?? "null"); - + else + { + m_log.ErrorFormat("{0} JoinRoom. Failed to join room {1}", LogHeader, RoomId); + if (m_log.IsDebugEnabled) + m_log.DebugFormat("{0} JoinRoom. Failure detail: {1}", LogHeader, joinResp?.ToString() ?? "null"); + } } } catch (Exception e) { m_log.Error($"{LogHeader} JoinRoom. Exception ", e); } - return ret; + return false; } private async Task RecoverAlreadyInRoomAndLeave(string pDisplay) @@ -222,7 +221,6 @@ namespace osWebRtcVoice public async Task LeaveRoom(JanusViewerSession pAttendeeSession) { - bool ret = false; try { JanusMessageResp resp = await _AudioBridge.SendPluginMsg( @@ -238,33 +236,31 @@ namespace osWebRtcVoice string returnCode = abResp.AudioBridgeReturnCode; string janusReturnCode = resp.ReturnCode; int errorCode = abResp.AudioBridgeErrorCode; - bool isBenignAlreadyLeft = errorCode == 487 && - (returnCode == "event" || janusReturnCode == "event" || janusReturnCode == "ack"); if (errorCode == 0 && (abResp.isSuccess || returnCode == "left" || returnCode == "event" || returnCode == "success" || janusReturnCode == "ack")) { - ret = true; if (janusReturnCode == "ack" && string.IsNullOrEmpty(returnCode)) { - m_log.Debug($"{LogHeader} LeaveRoom. Ack accepted for room {RoomId}, participant={pAttendeeSession.ParticipantId}"); + m_log.Debug($"{LogHeader} LeaveRoom. Ack for room {RoomId}, participant={pAttendeeSession.ParticipantId}"); } + return true; } - else if (isBenignAlreadyLeft) + + if (errorCode == 487 && + (returnCode == "event" || janusReturnCode == "event" || janusReturnCode == "ack")) { - ret = true; m_log.Info($"{LogHeader} LeaveRoom. Participant already left room {RoomId}, participant={pAttendeeSession.ParticipantId} (errorCode=487)"); + return true; } - else - { - m_log.Error($"{LogHeader} LeaveRoom. Failed room {RoomId}, participant={pAttendeeSession.ParticipantId}, janus={janusReturnCode}, audiobridge={returnCode}, errorCode={errorCode}"); - } + + m_log.Error($"{LogHeader} LeaveRoom. Failed room {RoomId}, participant={pAttendeeSession.ParticipantId}, janus={janusReturnCode}, audiobridge={returnCode}, errorCode={errorCode}"); } catch (Exception e) { m_log.Error($"{LogHeader} LeaveRoom. Exception ", e); } - return ret; + return false; } } } diff --git a/OpenSim/Addons/os-webrtc-janus/Janus/JanusViewerSession.cs b/OpenSim/Addons/os-webrtc-janus/Janus/JanusViewerSession.cs index 7ab3e801c9..bb6e3185d5 100644 --- a/OpenSim/Addons/os-webrtc-janus/Janus/JanusViewerSession.cs +++ b/OpenSim/Addons/os-webrtc-janus/Janus/JanusViewerSession.cs @@ -119,8 +119,11 @@ namespace osWebRtcVoice { JanusSession s = Session; Session = null; - _ = await s.DestroySession().ConfigureAwait(false); - s.Dispose(); + if(s != null) + { + _ = await s.DestroySession().ConfigureAwait(false); + s.Dispose(); + } } } } diff --git a/OpenSim/Addons/os-webrtc-janus/Janus/WebRtcJanusService.cs b/OpenSim/Addons/os-webrtc-janus/Janus/WebRtcJanusService.cs index eb74403d37..932f003238 100644 --- a/OpenSim/Addons/os-webrtc-janus/Janus/WebRtcJanusService.cs +++ b/OpenSim/Addons/os-webrtc-janus/Janus/WebRtcJanusService.cs @@ -164,12 +164,12 @@ namespace osWebRtcVoice // Requests through the capabilities will create rooms janusSession.AddPlugin(audioBridge); - pViewerSession.VoiceServiceSessionId = janusSession.SessionId; pViewerSession.Session = janusSession; pViewerSession.AudioBridge = audioBridge; janusSession.OnDisconnect += Handle_Hangup; janusSession.OnHangup += Handle_Hangup; + return true; } _log.Error($"{LogHeader} JanusPluginHandle not created"); @@ -245,9 +245,9 @@ namespace osWebRtcVoice _log.InfoFormat("{0} ProvisionVoiceAccountRequest: disconnected by {1}. agent={2}, scene={3}, room={4}, participant={5}, viewer_session={6}", LogHeader, pReason, pViewerSession.AgentId, pViewerSession.RegionId, roomId, pViewerSession.ParticipantId, pViewerSession.ViewerSessionID); + VoiceViewerSession.RemoveViewerSession(pViewerSession.ViewerSessionID); Task.Run(() => { - VoiceViewerSession.RemoveViewerSession(pViewerSession.ViewerSessionID); // No need to wait for the session to be shutdown _ = pViewerSession.Shutdown(); }); @@ -265,23 +265,31 @@ namespace osWebRtcVoice public async Task ProvisionVoiceAccountRequestBAD(IVoiceViewerSession pSession, OSDMap pRequest, UUID pUserID, UUID pSceneID) { + long flowId = Interlocked.Increment(ref _VoiceFlowCounter); + if(pRequest.TryGetString("voice_server_type", out string voice_server_type)) + { + if(!"webrtc".Equals(voice_server_type,StringComparison. CurrentCultureIgnoreCase)) + { + _log.Error($"{LogHeader} ProvisionVoiceAccountRequest: invalid server type {voice_server_type ?? "null"}"); + return new OSDMap + { + { "response", "failed" }, + { "error", "Invalid server type" } + }; + } + } + OSDMap ret = null; string errorMsg = null; JanusViewerSession viewerSession = pSession as JanusViewerSession; - long flowId = Interlocked.Increment(ref _VoiceFlowCounter); + if (viewerSession is not null) { - if (viewerSession.Session is null) - { - // This is a new session so we must create a new session and handle to the audio bridge - await ConnectToSessionAndAudioBridge(viewerSession).ConfigureAwait(false); - } - // TODO: need to keep count of users in a room to know when to close a room bool isLogout = pRequest.TryGetBool("logout", out bool lgout) && lgout; if (isLogout) { - // The client is logging out. Exit the room. + // Exit the room. if (viewerSession.Room is not null) { _ = await viewerSession.Room.LeaveRoom(viewerSession).ConfigureAwait(false); @@ -296,14 +304,20 @@ namespace osWebRtcVoice }; } + if (viewerSession.Session is null) + { + // This is a new session so we must create a new session and handle to the audio bridge + await ConnectToSessionAndAudioBridge(viewerSession).ConfigureAwait(false); + } + // Get the parameters that select the room // To get here, voice_server_type has already been checked to be 'webrtc' and channel_type='local' int parcel_local_id = pRequest.TryGetInt("parcel_local_id", out int pli) ? pli : JanusAudioBridge.REGION_ROOM_ID; string channel_id = pRequest.TryGetString("channel_id", out string cli) ? cli : string.Empty; string channel_credentials = pRequest.TryGetString("credentials", out string cred) ? cred : string.Empty; string channel_type = pRequest["channel_type"].AsString(); + string gridHash = pRequest.TryGetString("gridhash", out string ghash) ? ghash : string.Empty; bool isSpatial = channel_type == "local"; - string voice_server_type = pRequest["voice_server_type"].AsString(); _log.DebugFormat("{0} ProvisionVoiceAccountRequest: parcel_id={1} channel_id={2} channel_type={3} voice_server_type={4}", LogHeader, parcel_local_id, channel_id, channel_type, voice_server_type); @@ -320,14 +334,15 @@ namespace osWebRtcVoice { // The client is sending an offer. Find the right room and join it. // _log.DebugFormat("{0} ProvisionVoiceAccountRequest: jsep type={1} sdp={2}", LogHeader, jsepType, jsepSdp); - viewerSession.Room = await viewerSession.AudioBridge.SelectRoom(pSceneID.ToString(), - channel_type, isSpatial, parcel_local_id, channel_id).ConfigureAwait(false); + viewerSession.Room = await viewerSession.AudioBridge.SelectRoom(pSceneID.ToString(), gridHash, + channel_type, isSpatial, parcel_local_id, channel_id, channel_credentials).ConfigureAwait(false); if (viewerSession.Room is null) { errorMsg = "room selection failed"; _log.Error($"{LogHeader} ProvisionVoiceAccountRequest: room selection failed"); } - else { + else + { viewerSession.Offer = jsepSdp; viewerSession.OfferOrig = jsepSdp; viewerSession.AgentId = pUserID; @@ -399,7 +414,7 @@ namespace osWebRtcVoice JanusViewerSession viewerSession = pSession as JanusViewerSession; JanusMessageResp resp = null; long flowId = Interlocked.Increment(ref _VoiceFlowCounter); - if (viewerSession is not null) + if (viewerSession is not null && viewerSession.Session is not null) { // The request should be an array of candidates if (pRequest.TryGetOSDMap("candidate", out OSDMap candidate)) diff --git a/OpenSim/Addons/os-webrtc-janus/WebRtcVoiceRegionModule/WebRtcVoiceRegionModule.cs b/OpenSim/Addons/os-webrtc-janus/WebRtcVoiceRegionModule/WebRtcVoiceRegionModule.cs index f1afd9c6d4..6329156a17 100644 --- a/OpenSim/Addons/os-webrtc-janus/WebRtcVoiceRegionModule/WebRtcVoiceRegionModule.cs +++ b/OpenSim/Addons/os-webrtc-janus/WebRtcVoiceRegionModule/WebRtcVoiceRegionModule.cs @@ -79,6 +79,8 @@ namespace osWebRtcVoice private IWebRtcVoiceService m_spatialVoiceService; private IWebRtcVoiceService m_nonSpatialVoiceService; + private UUID gridHash = UUID.Zero; + // ISharedRegionModule.Initialize public void Initialise(IConfigSource config) { @@ -175,6 +177,14 @@ namespace osWebRtcVoice scene.EventManager.OnNewClient += OnNewClient; + if(gridHash.IsZero()) + { + if(!string.IsNullOrEmpty(scene.SceneGridInfo.GridUrl)) + gridHash = Util.ComputeShake128UUID(scene.SceneGridInfo.GridUrl + scene.SceneGridInfo.GridName); + else if (!string.IsNullOrEmpty(scene.SceneGridInfo.HomeURL + scene.SceneGridInfo.GridName)) + gridHash = Util.ComputeShake128UUID(scene.SceneGridInfo.HomeURLNoEndSlash); + } + ISimulatorFeaturesModule simFeatures = scene.RequestModuleInterface(); simFeatures?.AddFeature("VoiceServerType", OSD.FromString("webrtc")); } @@ -552,7 +562,7 @@ namespace osWebRtcVoice } else { - flags = IVoiceViewerSession.VFlags.IsEstate; + flags = IVoiceViewerSession.VFlags.IsEstate; } // TODO: check if this userId is making a new session (case that user is reconnecting) @@ -583,6 +593,7 @@ namespace osWebRtcVoice { vSession.Flags = IVoiceViewerSession.VFlags.IsAdmin; VoiceViewerSession.AddViewerSession(vSession); + map["gridhash"] = gridHash; } } }