package im.rosetta.executors; import java.util.HashMap; import java.util.HashSet; import java.util.Set; import java.util.UUID; import im.rosetta.Failures; import im.rosetta.calls.CallManager; import im.rosetta.calls.CallSession; import im.rosetta.client.ClientManager; import im.rosetta.client.tags.ECIAuthentificate; import im.rosetta.packet.Packet26SignalPeer; import im.rosetta.packet.runtime.NetworkSignalType; import im.rosetta.service.dispatch.push.PushNotifyDispatcher; import im.rosetta.service.dispatch.runtime.PushType; import im.rosetta.service.services.ForwardUnitService; import io.g365sfu.Room; import io.orprotocol.ProtocolException; import io.orprotocol.client.Client; import io.orprotocol.packet.PacketExecutor; /** * Используется в Peer To Peer звонках, в групповых звонках другой сигналинг */ public class Executor26SignalPeer extends PacketExecutor { private ClientManager clientManager; private ForwardUnitService fus; private PushNotifyDispatcher pushNotifyDispatcher = new PushNotifyDispatcher(); /** * Сигналы, которые может выполнять только авторизованный пользователь, * все сигналы, которые не входят в этот перечень, будут доступны для * исполнения без авторизации. */ private Set authentificatedTypes = new HashSet<>(){{ add(NetworkSignalType.CALL); }}; /** * Менеджер звонков, который реализует весь необхоимый функционал для управления звонками, * например проверку занятости пользователя, и тд */ private CallManager callManager; public Executor26SignalPeer(ClientManager clientManager, ForwardUnitService fus, CallManager callManager) { this.clientManager = clientManager; this.fus = fus; this.callManager = callManager; } @Override public void onPacketReceived(Packet26SignalPeer packet, Client client) throws Exception, ProtocolException { String src = packet.getSrc(); String dst = packet.getDst(); ECIAuthentificate eciAuthentificate = client.getTag(ECIAuthentificate.class); NetworkSignalType type = packet.getSignalType(); if ((eciAuthentificate == null || !eciAuthentificate.hasAuthorized()) && this.authentificatedTypes.contains(type)) { /** * Если клиент не авторизован, то мы не будем обрабатывать его сигналы на инициализацию звонка и создание комнаты * и просто отключим его от сервера. */ client.disconnect(Failures.HANDSHAKE_NOT_COMPLETED); return; } if(src != null && !src.equals(eciAuthentificate.getPublicKey()) && authentificatedTypes.contains(type)) { /** * Если src в пакете не совпадает с авторизованным PK клиента, то это может означать, что клиент пытается * отправить сигнал от другого пользователя, отключаем его от сервера. */ client.disconnect(Failures.DATA_MISSMATCH); return; } if(type == NetworkSignalType.CALL) { /** * Инициируется звонок от src к dst, проверяем, что dst не занят другим звонком, если занят, то отправляем сигнал END_CALL_BECAUSE_BUSY обратно src */ if(this.callManager.isBusy(dst) || this.callManager.isBusy(src)) { /** * Получатель сигнала уже находится в другой комнате, значит он занят другим звонком, отправляем сигнал END_CALL_BECAUSE_BUSY обратно src */ Packet26SignalPeer responsePacket = new Packet26SignalPeer(); responsePacket.setSignalType(NetworkSignalType.END_CALL_BECAUSE_BUSY); client.send(responsePacket); return; } /** * Генерируем CallID и JoinToken */ String callId = UUID.randomUUID().toString(); String joinToken = UUID.randomUUID().toString(); packet.setJoinToken(joinToken); packet.setCallId(callId); /** * Создаем сессию звонка и добавляем в нее звонящего */ CallSession session = this.callManager.createCall(callId, joinToken); session.joinCall(src, client); /** * Добавляем dst в ringing, чтобы пометить, что ему поступает звонок */ session.addRinging(dst); /** * Получатель сигнала не занят, отправляем ему пуш уведомление о входящем звонке и сигнал CALL для инициализации звонка */ pushNotifyDispatcher.sendPush(dst, new HashMap<>(){{ put("type", PushType.CALL); put("dialog", src); put("callId", callId); put("joinToken", joinToken); }}); /** * Отправляем сигнал CALL всем авторизованным устройствам вызываемого абонента */ this.clientManager.sendPacketToAuthorizedPK(dst, packet); return; } if(type == NetworkSignalType.ACCEPT){ String callId = packet.getCallId(); String joinToken = packet.getJoinToken(); CallSession session = this.callManager.getCallSession(callId, joinToken); if(session == null) { /** * Сессии звонка нет */ client.disconnect(Failures.NO_CALL_SESSION); return; } if(!session.isValidSource(src)) { /** * Клиент не состоит в сессии звонка, отключаем его от сервера, так как он отправляет некорректные данные */ client.disconnect(Failures.DATA_MISSMATCH); return; } Room room = this.fus.createRoom(); session.setRoom(room); session.joinCall(src, client); for(String participant : session.clients.keySet()) { room.addParticipant(participant); } session.sendPacket(packet, client); /** * Сбрасываем вызов на всех остальных устройствах пользователя, который принимает звонок, * чтобы он не смог принять или отклонить звонок с другого устройства */ Packet26SignalPeer endCallOtherDevices = new Packet26SignalPeer(); endCallOtherDevices.setSignalType(NetworkSignalType.END_CALL); this.clientManager.sendPacketToAuthorizedPK(src, endCallOtherDevices, client); return; } if(type == NetworkSignalType.KEY_EXCHANGE){ /** * Ретранслируем ключи в рамках сессии */ CallSession session = this.callManager.getCallSession(client); if(session == null) { /** * Сессии звонка нет */ client.disconnect(Failures.NO_CALL_SESSION); return; } /** * Обмениваемся ключами в рамках сессии, ретранслируя их всем участникам сессии, кроме отправителя */ session.sendPacket(packet, client); return; } if(type == NetworkSignalType.END_CALL) { /** * Ретранслируем сигнал окончания звонка всем участникам сессии, кроме отправителя, и удаляем сессию */ /** * Сначала получаем сессию по сокету отправителя пакета, если не находим, то пробуем найти сессию по callId и joinToken из пакета, если не находим, * то отключаем клиента от сервера, так как он отправляет некорректные данные */ CallSession session = this.callManager.getCallSession(client); if(session == null) { String callId = packet.getCallId(); String joinToken = packet.getJoinToken(); session = this.callManager.getCallSession(callId, joinToken); } if(session == null) { /** * Сессии звонка нет, скорее всего она была удалена при обрыве RTC Peer Connection, * при срабатывании RTCPeerConnection::close на клиенте раньше, чем клиент отправил сигнал END_CALL */ return; } /** * Отправляем сигнал окончания звонка всем участникам сессии, кроме отправителя */ session.sendPacket(packet, client); /** * Отправляем пакет вызываемым (ringing) пользователям (которые еще не в сессии) */ this.callManager.sendPacketToRinging(session, packet); /** * Удаляем сессию из активных сессий звонков */ this.callManager.removeSession(session); return; } if(type == NetworkSignalType.ACTIVE) { /** * Клиент сообщил, что прошел стадию обмена ключами и звонок активен */ CallSession session = this.callManager.getCallSession(client); if(session == null) { /** * Сессии звонка нет */ client.disconnect(Failures.NO_CALL_SESSION); return; } session.sendPacket(packet, null); } } }