content: /*******************************************************************************
   * Software Name : RCS IMS Stack
   *
   * Copyright (C) 2010-2016 Orange.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   ******************************************************************************/
  
  package com.orangelabs.rcs.core.ims.protocol.rtp.core;
  
  import java.io.ByteArrayInputStream;
  import java.io.DataInputStream;
  import java.io.IOException;
  import java.net.SocketTimeoutException;
  import java.util.Vector;
  
  import com.orangelabs.rcs.core.ims.protocol.rtp.event.RtcpApplicationEvent;
  import com.orangelabs.rcs.core.ims.protocol.rtp.event.RtcpByeEvent;
  import com.orangelabs.rcs.core.ims.protocol.rtp.event.RtcpEvent;
  import com.orangelabs.rcs.core.ims.protocol.rtp.event.RtcpEventListener;
  import com.orangelabs.rcs.core.ims.protocol.rtp.event.RtcpReceiverReportEvent;
  import com.orangelabs.rcs.core.ims.protocol.rtp.event.RtcpSdesEvent;
  import com.orangelabs.rcs.core.ims.protocol.rtp.event.RtcpSenderReportEvent;
  import com.orangelabs.rcs.core.ims.protocol.rtp.util.AndroidDatagramConnection;
  import com.orangelabs.rcs.core.ims.protocol.rtp.util.DatagramConnection;
  import com.orangelabs.rcs.core.ims.protocol.rtp.util.Packet;
  
  /**
   * RTCP packet receiver
   * 
   * @author jexa7410
   */
  public class RtcpPacketReceiver extends Thread {
      /**
       * Datagram connection
       */
      public DatagramConnection datagramConnection = null;
  
      /**
       * Statistics
       */
      private RtcpStatisticsReceiver stats = new RtcpStatisticsReceiver();
  
      /**
       * RTCP event listeners
       */
      private Vector<RtcpEventListener> listeners = new Vector<RtcpEventListener>();
  
      /**
       * RTCP Session
       */
      private RtcpSession rtcpSession = null;
  
      /**
       * Constructor
       * 
       * @param port Listening port
       * @param rtcpSession the RTCP session
       * @param socketTimeout
       * @throws IOException
       */
      public RtcpPacketReceiver(int port, RtcpSession rtcpSession, int socketTimeout)
              throws IOException {
          super();
  
          this.rtcpSession = rtcpSession;
  
          // Create the UDP server
          datagramConnection = new AndroidDatagramConnection(socketTimeout);
          datagramConnection.open(port);
      }
  
      /**
       * Constructor
       * 
       * @param port Listening port
       * @param rtcpSession the RTCP session
       * @throws IOException
       */
      public RtcpPacketReceiver(int port, RtcpSession rtcpSession) throws IOException {
          this(port, rtcpSession, 0);
      }
  
      /**
       * Close the receiver
       * 
       * @throws IOException
       */
      public void close() throws IOException {
          // Interrupt the current thread processing
          try {
              interrupt();
          } catch (Exception e) {
          }
  
          // Close the datagram connection
          if (datagramConnection != null) {
              datagramConnection.close();
              datagramConnection = null;
          }
      }
  
      /**
       * Background processing
       */
      public void run() {
          try {
              while (datagramConnection != null) {
                  // Wait a packet
                  byte[] data = datagramConnection.receive();
  
                  // Create a packet object
                  Packet packet = new Packet();
                  packet.data = data;
                  packet.length = data.length;
                  packet.offset = 0;
                  packet.receivedAt = System.currentTimeMillis();
  
                  // Process the received packet
                  handlePacket(packet);
              }
          } catch (SocketTimeoutException ex) {
              notifyRtcpListenersOfTimeout();
          } catch (Exception e) {
              e.printStackTrace();
          }
      }
  
      /**
       * Handle the received packet
       * 
       * @param packet Packet
       * @return RTCP packet
       */
      public RtcpPacket handlePacket(Packet p) {
          // Update statistics
          stats.numRtcpPkts++;
          stats.numRtcpBytes += p.length;
  
          // Parse the RTCP packet
          RtcpPacket result;
          try {
              result = parseRtcpPacket(p);
          } catch (Exception e) {
              stats.numBadRtcpPkts++;
              return null;
          }
          return result;
      }
  
      /**
       * Parse the RTCP packet
       * 
       * @param packet RTCP packet not yet parsed
       * @return RTCP packet
       */
      public RtcpPacket parseRtcpPacket(Packet packet) {
          RtcpCompoundPacket compoundPacket = new RtcpCompoundPacket(packet);
          Vector<RtcpPacket> subpackets = new Vector<RtcpPacket>();
          DataInputStream in = new DataInputStream(
                  new ByteArrayInputStream(compoundPacket.data,
                          compoundPacket.offset,
                          compoundPacket.length));
          try {
              rtcpSession.updateavgrtcpsize(compoundPacket.length);
              int length = 0;
              for (int offset = 0; offset < compoundPacket.length; offset += length) {
                  // Read first byte
                  int firstbyte = in.readUnsignedByte();
                  if ((firstbyte & 0xc0) != 128) {
                      return null;
                  }
  
                  // Read type of subpacket
                  int type = in.readUnsignedByte();
  
                  // Read length of subpacket
                  length = in.readUnsignedShort();
                  length = length + 1 << 2;
                  int padlen = 0;
                  if (offset + length > compoundPacket.length) {
                      return null;
                  }
                  if (offset + length == compoundPacket.length) {
                      if ((firstbyte & 0x20) != 0) {
                          padlen = compoundPacket.data[compoundPacket.offset + compoundPacket.length
                                  - 1] & 0xff;
                          if (padlen == 0) {
                              return null;
                          }
                      }
                  } else if ((firstbyte & 0x20) != 0) {
                      return null;
                  }
                  int inlength = length - padlen;
                  firstbyte &= 0x1f;
  
                  // Parse subpacket
                  RtcpPacket subpacket;
                  switch (type) {
                  // RTCP SR event
                      case RtcpPacket.RTCP_SR:
                          stats.numSrPkts++;
                          if (inlength != 28 + 24 * firstbyte) {
                              stats.numMalformedRtcpPkts++;
                              return null;
                          }
                          RtcpSenderReportPacket srp = new RtcpSenderReportPacket(compoundPacket);
                          subpacket = srp;
                          srp.ssrc = in.readInt();
                          srp.ntptimestampmsw = (long) in.readInt() & 0xffffffffL;
                          srp.ntptimestamplsw = (long) in.readInt() & 0xffffffffL;
                          srp.rtptimestamp = (long) in.readInt() & 0xffffffffL;
                          srp.packetcount = (long) in.readInt() & 0xffffffffL;
                          srp.octetcount = (long) in.readInt() & 0xffffffffL;
                          srp.reports = new RtcpReport[firstbyte];
  
                          RtpSource sourceSR = rtcpSession.getMySource();
                          if (sourceSR != null) {
                              sourceSR.receivedSenderReport(srp);
                          }
  
                          for (int i = 0; i < srp.reports.length; i++) {
                              RtcpReport report = new RtcpReport();
                              srp.reports[i] = report;
                              report.ssrc = in.readInt();
                              long val = in.readInt();
                              val &= 0xffffffffL;
                              report.fractionlost = (int) (val >> 24);
                              report.packetslost = (int) (val & 0xffffffL);
                              report.lastseq = (long) in.readInt() & 0xffffffffL;
                              report.jitter = in.readInt();
                              report.lsr = (long) in.readInt() & 0xffffffffL;
                              report.dlsr = (long) in.readInt() & 0xffffffffL;
                          }
  
                          // Notify event listeners
                          notifyRtcpListeners(new RtcpSenderReportEvent(srp));
                          break;
  
                      // RTCP RR event
                      case RtcpPacket.RTCP_RR:
                          if (inlength != 8 + 24 * firstbyte) {
                              stats.numMalformedRtcpPkts++;
                              return null;
                          }
                          RtcpReceiverReportPacket rrp = new RtcpReceiverReportPacket(compoundPacket);
                          subpacket = rrp;
                          rrp.ssrc = in.readInt();
                          rrp.reports = new RtcpReport[firstbyte];
  
                          for (int i = 0; i < rrp.reports.length; i++) {
                              RtcpReport report = new RtcpReport();
                              rrp.reports[i] = report;
                              report.ssrc = in.readInt();
                              long val = in.readInt();
                              val &= 0xffffffffL;
                              report.fractionlost = (int) (val >> 24);
                              report.packetslost = (int) (val & 0xffffffL);
                              report.lastseq = (long) in.readInt() & 0xffffffffL;
                              report.jitter = in.readInt();
                              report.lsr = (long) in.readInt() & 0xffffffffL;
                              report.dlsr = (long) in.readInt() & 0xffffffffL;
                          }
  
                          // Notify event listeners
                          notifyRtcpListeners(new RtcpReceiverReportEvent(rrp));
                          break;
  
                      // RTCP SDES event
                      case RtcpPacket.RTCP_SDES:
                          RtcpSdesPacket sdesp = new RtcpSdesPacket(compoundPacket);
                          subpacket = sdesp;
                          sdesp.sdes = new RtcpSdesBlock[firstbyte];
                          int sdesoff = 4;
                          for (int i = 0; i < sdesp.sdes.length; i++) {
                              RtcpSdesBlock chunk = new RtcpSdesBlock();
                              sdesp.sdes[i] = chunk;
                              chunk.ssrc = in.readInt();
                              sdesoff += 5;
                              Vector<RtcpSdesItem> items = new Vector<RtcpSdesItem>();
                              boolean gotcname = false;
                              int j;
                              while ((j = in.readUnsignedByte()) != 0) {
                                  if (j < 1 || j > 8) {
                                      stats.numMalformedRtcpPkts++;
                                      return null;
                                  }
                                  if (j == 1) {
                                      gotcname = true;
                                  }
                                  RtcpSdesItem item = new RtcpSdesItem();
                                  items.addElement(item);
                                  item.type = j;
                                  int sdeslen = in.readUnsignedByte();
                                  item.data = new byte[sdeslen];
                                  in.readFully(item.data);
                                  sdesoff += 2 + sdeslen;
                              }
                              if (!gotcname) {
                                  stats.numMalformedRtcpPkts++;
                                  return null;
                              }
                              chunk.items = new RtcpSdesItem[items.size()];
                              items.copyInto(chunk.items);
                              if ((sdesoff & 3) != 0) {
                                  if (in.skip(4 - (sdesoff & 3)) != 4 - (sdesoff & 3)) {
                                      return null;
                                  }
                                  sdesoff = sdesoff + 3 & -4;
                              }
                          }
  
                          if (inlength != sdesoff) {
                              stats.numMalformedRtcpPkts++;
                              return null;
                          }
  
                          // Notify event listeners
                          notifyRtcpListeners(new RtcpSdesEvent(sdesp));
                          break;
  
                      // RTCP BYE event
                      case RtcpPacket.RTCP_BYE:
                          RtcpByePacket byep = new RtcpByePacket(compoundPacket);
                          subpacket = byep;
                          byep.ssrc = new int[firstbyte];
                          for (int i = 0; i < byep.ssrc.length; i++) {
                              byep.ssrc[i] = in.readInt();
                          }
  
                          int reasonlen;
                          if (inlength > 4 + 4 * firstbyte) {
                              reasonlen = in.readUnsignedByte();
                              byep.reason = new byte[reasonlen];
                              reasonlen++;
                          } else {
                              reasonlen = 0;
                              byep.reason = new byte[0];
                          }
                          reasonlen = reasonlen + 3 & -4;
                          if (inlength != 4 + 4 * firstbyte + reasonlen) {
                              stats.numMalformedRtcpPkts++;
                              return null;
                          }
                          in.readFully(byep.reason);
                          int skipBye = reasonlen - byep.reason.length;
                          if (in.skip(skipBye) != skipBye) {
                              return null;
                          }
  
                          // Notify event listeners
                          notifyRtcpListeners(new RtcpByeEvent(byep));
                          break;
  
                      // RTCP APP event
                      case RtcpPacket.RTCP_APP:
                          if (inlength < 12) {
                              return null;
                          }
                          RtcpAppPacket appp = new RtcpAppPacket(compoundPacket);
                          subpacket = appp;
                          appp.ssrc = in.readInt();
                          appp.name = in.readInt();
                          appp.subtype = firstbyte;
                          appp.data = new byte[inlength - 12];
                          in.readFully(appp.data);
                          int skipApp = inlength - 12 - appp.data.length;
                          if (in.skip(skipApp) != skipApp) {
                              return null;
                          }
  
                          // Notify event listeners
                          notifyRtcpListeners(new RtcpApplicationEvent(appp));
                          break;
  
                      // RTCP unknown event
                      default:
                          stats.numUnknownTypes++;
                          return null;
                  }
                  subpacket.offset = offset;
                  subpacket.length = length;
                  subpackets.addElement(subpacket);
                  if (in.skipBytes(padlen) != padlen) {
                      return null;
                  }
              }
  
          } catch (Exception e) {
              return null;
          }
          compoundPacket.packets = new RtcpPacket[subpackets.size()];
          subpackets.copyInto(compoundPacket.packets);
          return compoundPacket;
      }
  
      /**
       * Add a RTCP event listener
       * 
       * @param listener Listener
       */
      public void addRtcpListener(RtcpEventListener listener) {
          listeners.addElement(listener);
      }
  
      /**
       * Remove a RTCP event listener
       * 
       * @param listener Listener
       */
      public void removeRtcpListener(RtcpEventListener listener) {
          listeners.removeElement(listener);
      }
  
      /**
       * Notify RTCP event listeners
       * 
       * @param event RTCP event
       */
      public void notifyRtcpListeners(RtcpEvent event) {
          for (int i = 0; i < listeners.size(); i++) {
              RtcpEventListener listener = (RtcpEventListener) listeners.elementAt(i);
              listener.receiveRtcpEvent(event);
          }
      }
  
      /**
       * Notify timeout on RTCP listener
       */
      private void notifyRtcpListenersOfTimeout() {
          for (RtcpEventListener listener : listeners) {
              listener.connectionTimeout();
          }
      }
  
      /**
       * Returns the statistics of RTCP reception
       * 
       * @return Statistics
       */
      public RtcpStatisticsReceiver getRtcpReceptionStats() {
          return stats;
      }
  
      /**
       * Returns the DatagramConnection of RTCP
       * 
       * @return DatagramConnection
       */
      public DatagramConnection getConnection() {
          return datagramConnection;
      }
  }
  