/* * Copyright 2007 Sun Microsystems, Inc. * * This file is part of jVoiceBridge. * * jVoiceBridge is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation and distributed hereunder * to you. * * jVoiceBridge is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * Sun designates this particular file as subject to the "Classpath" * exception as provided by Sun in the License file that accompanied this * code. */ package com.sun.voip.server; import com.sun.voip.CallParticipant; import com.sun.voip.Logger; import com.sun.voip.RtpPacket; import com.sun.voip.RtpSocket; import java.io.IOException; import java.net.DatagramSocket; import java.net.InetSocketAddress; import java.net.SocketException; import java.nio.ByteBuffer; import java.nio.channels.DatagramChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.util.HashMap; import java.util.Iterator; import java.util.Vector; import com.sun.stun.StunServerImpl; /** * Receive data from each member in a conference and dispatch it to * the appropriate ConferenceMember so the data can be given to the mixer. */ public class ConferenceReceiver extends Thread { /* * For debugging */ private static int receiverPause = 0; // ms to pause private String conferenceId; private Selector selector; private StunServerImpl stunServerImpl; private boolean done; private static int loneReceiverPort = 0; private static DatagramChannel loneReceiverChannel; private int memberCount = 0; ConferenceReceiver(String conferenceId, int loneReceiverPort) throws SocketException { if (loneReceiverPort != 0) { conferenceId = "TheLoneReceiver"; setName(conferenceId); } else { setName("Receiver-" + conferenceId); } this.conferenceId = conferenceId; initLoneReceiverChannel(loneReceiverPort); stunServerImpl = new StunServerImpl(); start(); } private void initLoneReceiverChannel(int loneReceiverPort) { if (this.loneReceiverPort != loneReceiverPort && loneReceiverChannel != null) { close(); } this.loneReceiverPort = loneReceiverPort; try { selector = Selector.open(); } catch (IOException e) { Logger.println("Conference receiver failed to open selector " + e.getMessage()); return; } if (loneReceiverPort == 0) { return; } Logger.println("Init lone channel using port " + loneReceiverPort); try { loneReceiverChannel = DatagramChannel.open(); if (Logger.logLevel >= Logger.LOG_INFO) { Logger.println("Opened lone receiver channel " + loneReceiverChannel); } } catch (IOException e) { Logger.println( "Conference receiver failed to open DatagramChannel " + " " + e.getMessage()); return; } try { loneReceiverChannel.configureBlocking(false); } catch (IOException e) { Logger.println( "Conference receiver failed to configureBlocking to false " + e.getMessage()); return; } DatagramSocket socket = loneReceiverChannel.socket(); try { socket.setReceiveBufferSize(RtpSocket.MAX_RECEIVE_BUFFER); } catch (SocketException e) { Logger.println("ConferenceReceiver failed to set receive buffer size " + e.getMessage()); return; } try { socket.setSoTimeout(0); } catch (SocketException e) { Logger.println("ConferenceReceiver failed to set timeout " + e.getMessage()); return; } InetSocketAddress bridgeAddress = Bridge.getLocalBridgeAddress(); InetSocketAddress isa = new InetSocketAddress(bridgeAddress.getAddress(), loneReceiverPort); try { socket.bind(isa); } catch (IOException e) { Logger.println( "Conference receiver unable to bind to " + loneReceiverPort + " " + e.getMessage()); return; } try { SelectionKey selectionKey = loneReceiverChannel.register(selector, SelectionKey.OP_READ); } catch (Exception e) { Logger.println( "Conference receiver unable to register: " + e.getMessage()); return; } memberCount++; Logger.println("Lone Channel uses port " + loneReceiverPort); } public static DatagramChannel getChannel(CallParticipant cp) { if (loneReceiverChannel == null || cp.getPhoneNumber().indexOf("@") < 0) { return null; } return loneReceiverChannel; } /* * We're not sure of the selector synchronization issues so we add * members to register to a vector and have the thread below actually * do the register. */ private Vector<ConferenceMember> membersToRegister = new Vector(); private Vector<ConferenceMember> membersToUnregister = new Vector(); private HashMap<InetSocketAddress, MemberReceiver> members = new HashMap(); /* * Find the MemberReceiver associated with the InetSocketAddress of the sender. * RTP header. */ private MemberReceiver findMemberReceiver(InetSocketAddress isa) { synchronized (members) { return members.get(isa); } } public void addMember(MemberReceiver memberReceiver) { synchronized (members) { Logger.println("addMember " + memberReceiver + " " + memberReceiver.getMember().getMemberSender().getSendAddress()); members.put(memberReceiver.getMember().getMemberSender().getSendAddress(), memberReceiver); } } public void addMember(ConferenceMember member) throws IOException { CallParticipant cp = member.getCallParticipant(); if (loneReceiverChannel != null && cp.getPhoneNumber().indexOf("@") >= 0) { return; } synchronized(membersToRegister) { if (selector == null) { return; } membersToRegister.add(member); Logger.writeFile("ConferenceReceiver Adding member to register " + member + " size " + membersToRegister.size()); selector.wakeup(); } } public void removeMember(ConferenceMember member) { CallParticipant cp = member.getCallParticipant(); if (loneReceiverChannel != null) { synchronized (members) { if (members.remove(member.getMemberSender().getSendAddress()) != null) { return; } } } synchronized(membersToRegister) { if (selector == null) { return; } membersToUnregister.add(member); Logger.writeFile("ConferenceReceiver adding member to unregister " + member + " size " + membersToUnregister.size()); selector.wakeup(); } } private void registerMembers() { synchronized(membersToRegister) { for (int i = 0; i < membersToRegister.size(); i++) { ConferenceMember member = (ConferenceMember) membersToRegister.get(i); Logger.writeFile("ConferenceReceiver registering " + member); try { member.getMemberReceiver().register(selector); memberCount++; } catch (Exception e) { Logger.println( "ConferenceReceiver failed to register member " + member + " " + e.getMessage()); membersToRegister.remove(member); if (member.getCallHandler() != null) { member.getCallHandler().cancelRequest( "ConferenceReceiver failed to register member "); } } } membersToRegister.clear(); for (int i = 0; i < membersToUnregister.size(); i++) { ConferenceMember member = (ConferenceMember) membersToUnregister.get(i); Logger.writeFile("ConferenceReceiver unregistering " + member); member.getMemberReceiver().unregister(); memberCount--; } membersToUnregister.clear(); } } /** * Receive data and dispatch the data to the appropriate member. */ public void run() { while (!done) { try { registerMembers(); /* * Wait for packets to arrive */ int n; if ((n = selector.select()) <= 0) { if (Logger.logLevel == -1) { Logger.println("select returned " + n + " isOpen " + selector.isOpen()); Logger.println("membersToRegister size " + membersToRegister.size() + " membersToUnregister size " + membersToUnregister.size()); Logger.println("keys size " + selector.keys().size() + " member count " + memberCount); } continue; } if (Logger.logLevel == -1) { if (memberCount != selector.keys().size()) { Logger.println("memberCount " + memberCount + " not equal to selector key count " + selector.keys().size()); } } Iterator it = selector.selectedKeys().iterator(); byte[] data = new byte[RtpPacket.getMaxDataSize()]; int dataLength; InetSocketAddress isa; MemberReceiver memberReceiver; while (it.hasNext()) { try { SelectionKey sk = (SelectionKey)it.next(); it.remove(); DatagramChannel datagramChannel = (DatagramChannel)sk.channel(); ByteBuffer byteBuffer = ByteBuffer.wrap(data); isa = (InetSocketAddress) datagramChannel.receive(byteBuffer); dataLength = byteBuffer.position(); if (isStunBindingRequest(data) == true) { stunServerImpl.processStunRequest(datagramChannel, isa, data); continue; } memberReceiver = (MemberReceiver) sk.attachment(); if (memberReceiver == null) { memberReceiver = findMemberReceiver(isa); if (memberReceiver == null) { if (Logger.logLevel > Logger.LOG_DETAILINFO) { Logger.println("ConferenceReceiver couldn't find " + "member associated with packet! " + isa); } continue; } } if (memberReceiver.readyToReceiveData() == false) { if (memberReceiver.traceCall() || Logger.logLevel == -11) { Logger.println("receiver not ready, conference " + conferenceId + " " + memberReceiver + " address " + memberReceiver.getReceiveAddress()); } continue; } } catch (NullPointerException e) { e.printStackTrace(); /* * It's possible to get a null pointer exception when * end is called. The way to avoid this non-fatal error * is to synchronize on selector. * Catching the exception eliminates the overhead * of synchonization in the main receiver loop. */ if (!done) { Logger.println( "ConferenceReceiver: non-fatal NPE."); } System.exit(1); continue; } if (memberReceiver.traceCall()) { Logger.println("Received data for " + memberReceiver); } long start = 0; if (memberReceiver.traceCall()) { start = System.nanoTime(); } /* * Dispatch to member */ memberReceiver.receive(isa, data, dataLength); if (memberReceiver.traceCall()) { memberReceiver.traceCall(false); Logger.println("Call " + memberReceiver + " receive time " + ((System.nanoTime() - start) / 1000000000.) + " seconds"); } } /* * XXX For debugging */ if (receiverPause != 0) { if (receiverPause >= 20) { Logger.println("pause Receiving " + receiverPause + "ms"); } long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start < receiverPause) ; if (receiverPause >= 20) { receiverPause = 0; } } } catch (IOException e) { if (!done) { /* * We're not sure why this happens but there appears to be * a timing problem with selectors when a call ends. */ Logger.error("ConferenceReceiver: receive failed! " + e.getMessage()); e.printStackTrace(); } } catch (Exception e) { if (!done) { Logger.error("ConferenceReceiver: unexpected exception " + e.getMessage()); e.printStackTrace(); } } } } private boolean isStunBindingRequest(byte[] data) { /* * If this is an RTP packet, the first byte * must have bit 7 set indicating RTP v2. * If byte 0 is 0 and byte 1 is 1, then we * assume this packet is a STUN Binding request. */ return data[0] == 0 && data[1] == 1; } public static void setReceiverPause(int receiverPause) { ConferenceReceiver.receiverPause = receiverPause; } /* * finished */ public void end() { Logger.writeFile("Conference receiver done " + conferenceId); done = true; close(); } private void close() { synchronized(membersToRegister) { if (selector != null) { try { selector.close(); } catch (IOException e) { Logger.println( "Conference receiver failed to close selector " + conferenceId + " " + e.getMessage()); } selector = null; } } if (loneReceiverChannel != null) { try { loneReceiverChannel.close(); if (Logger.logLevel >= Logger.LOG_INFO) { Logger.println("Closed lone receiver channel " + loneReceiverChannel); } } catch (Exception e) { Logger.println("Exception closing lone receiver channel: " + e.getMessage()); } } } }