/**********
This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)

This library 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 Lesser General Public License for
more details.

You should have received a copy of the GNU Lesser General Public License
along with this library; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
**********/
// "liveMedia"
// Copyright (c) 1996-2022 Live Networks, Inc.  All rights reserved.
// RTP Sources containing generic QuickTime stream data, as defined in
//     <http://developer.apple.com/quicktime/icefloe/dispatch026.html>
// Implementation

#include "QuickTimeGenericRTPSource.hh"

///// QTGenericBufferedPacket and QTGenericBufferedPacketFactory /////

// A subclass of BufferedPacket, used to separate out
// individual frames (when PCK == 2)

class QTGenericBufferedPacket: public BufferedPacket {
public:
  QTGenericBufferedPacket(QuickTimeGenericRTPSource& ourSource);
  virtual ~QTGenericBufferedPacket();

private: // redefined virtual functions
  virtual unsigned nextEnclosedFrameSize(unsigned char*& framePtr,
					 unsigned dataSize);
private:
  QuickTimeGenericRTPSource& fOurSource;
};

class QTGenericBufferedPacketFactory: public BufferedPacketFactory {
private: // redefined virtual functions
  virtual BufferedPacket* createNewPacket(MultiFramedRTPSource* ourSource);
};


////////// QuickTimeGenericRTPSource //////////

QuickTimeGenericRTPSource*
QuickTimeGenericRTPSource::createNew(UsageEnvironment& env,
				     Groupsock* RTPgs,
				     unsigned char rtpPayloadFormat,
				     unsigned rtpTimestampFrequency,
				     char const* mimeTypeString) {
  return new QuickTimeGenericRTPSource(env, RTPgs, rtpPayloadFormat,
				       rtpTimestampFrequency,
				       mimeTypeString);
}

QuickTimeGenericRTPSource
::QuickTimeGenericRTPSource(UsageEnvironment& env, Groupsock* RTPgs,
			    unsigned char rtpPayloadFormat,
			    unsigned rtpTimestampFrequency,
			    char const* mimeTypeString)
  : MultiFramedRTPSource(env, RTPgs,
			 rtpPayloadFormat, rtpTimestampFrequency,
			 new QTGenericBufferedPacketFactory),
    fMIMEtypeString(strDup(mimeTypeString)) {
  qtState.PCK = 0;
  qtState.timescale = 0;
  qtState.sdAtom = NULL;
  qtState.sdAtomSize = qtState.width = qtState.height = 0;
}

QuickTimeGenericRTPSource::~QuickTimeGenericRTPSource() {
  delete[] qtState.sdAtom;
  delete[] (char*)fMIMEtypeString;
}

Boolean QuickTimeGenericRTPSource
::processSpecialHeader(BufferedPacket* packet,
                       unsigned& resultSpecialHeaderSize) {
  unsigned char* headerStart = packet->data();
  unsigned packetSize = packet->dataSize();

  // The "QuickTime Header" must be at least 4 bytes in size:
  // Extract the known fields from the first 4 bytes:
  unsigned expectedHeaderSize = 4;
  if (packetSize < expectedHeaderSize) return False;

  unsigned char VER = (headerStart[0]&0xF0)>>4;
  if (VER > 1) return False; // unknown header version
  qtState.PCK = (headerStart[0]&0x0C)>>2;
#ifdef DEBUG
  Boolean S = (headerStart[0]&0x02) != 0;
#endif
  Boolean Q = (headerStart[0]&0x01) != 0;

  Boolean L = (headerStart[1]&0x80) != 0;

#ifdef DEBUG
  Boolean D = (headerStart[2]&0x80) != 0;
  unsigned short payloadId = ((headerStart[2]&0x7F)<<8)|headerStart[3];
#endif
  headerStart += 4;

#ifdef DEBUG
  fprintf(stderr, "PCK: %d, S: %d, Q: %d, L: %d, D: %d, payloadId: %d\n", qtState.PCK, S, Q, L, D, payloadId);
#endif

  if (Q) { // A "QuickTime Payload Description" follows
    expectedHeaderSize += 4;
    if (packetSize < expectedHeaderSize) return False;

#ifdef DEBUG
    Boolean K = (headerStart[0]&0x80) != 0;
    Boolean F = (headerStart[0]&0x40) != 0;
    Boolean A = (headerStart[0]&0x20) != 0;
    Boolean Z = (headerStart[0]&0x10) != 0;
#endif
    unsigned payloadDescriptionLength = (headerStart[2]<<8)|headerStart[3];
    headerStart += 4;

#ifdef DEBUG
    fprintf(stderr, "\tK: %d, F: %d, A: %d, Z: %d, payloadDescriptionLength: %d\n", K, F, A, Z, payloadDescriptionLength);
#endif
    // Make sure "payloadDescriptionLength" is valid
    if (payloadDescriptionLength < 12) return False;
    expectedHeaderSize += (payloadDescriptionLength - 4);
    unsigned nonPaddedSize = expectedHeaderSize;
    expectedHeaderSize += 3;
    expectedHeaderSize -= expectedHeaderSize%4; // adds padding
    if (packetSize < expectedHeaderSize) return False;
    unsigned char padding = expectedHeaderSize - nonPaddedSize;

#ifdef DEBUG
    unsigned mediaType = (headerStart[0]<<24)|(headerStart[1]<<16)
      |(headerStart[2]<<8)|headerStart[3];
#endif
    qtState.timescale = (headerStart[4]<<24)|(headerStart[5]<<16)
      |(headerStart[6]<<8)|headerStart[7];
    headerStart += 8;

    payloadDescriptionLength -= 12;
#ifdef DEBUG
    fprintf(stderr, "\tmediaType: '%c%c%c%c', timescale: %d, %d bytes of TLVs left\n", mediaType>>24, (mediaType&0xFF0000)>>16, (mediaType&0xFF00)>>8, mediaType&0xFF, qtState.timescale, payloadDescriptionLength);
#endif

    while (payloadDescriptionLength > 3) {
      unsigned short tlvLength = (headerStart[0]<<8)|headerStart[1];
      unsigned short tlvType = (headerStart[2]<<8)|headerStart[3];
      payloadDescriptionLength -= 4;
      if (tlvLength > payloadDescriptionLength) return False; // bad TLV
      headerStart += 4;
#ifdef DEBUG
      fprintf(stderr, "\t\tTLV '%c%c', length %d, leaving %d remaining bytes\n", tlvType>>8, tlvType&0xFF, tlvLength, payloadDescriptionLength - tlvLength);
      for (int i = 0; i < tlvLength; ++i) fprintf(stderr, "%02x:", headerStart[i]); fprintf(stderr, "\n");
#endif

      // Check for 'TLV's that we can use for our 'qtState'
      switch (tlvType) {
      case ('s'<<8|'d'): { // session description atom
	// Sanity check: the first 4 bytes of this must equal "tlvLength":
	unsigned atomLength  = (headerStart[0]<<24)|(headerStart[1]<<16)
	  |(headerStart[2]<<8)|(headerStart[3]);
	if (atomLength != (unsigned)tlvLength) break;

	delete[] qtState.sdAtom; qtState.sdAtom = new char[tlvLength];
	memmove(qtState.sdAtom, headerStart, tlvLength);
	qtState.sdAtomSize = tlvLength;
	break;
      }
      case ('t'<<8|'w'): { // track width
	qtState.width = (headerStart[0]<<8)|headerStart[1];
	break;
      }
      case ('t'<<8|'h'): { // track height
	qtState.height = (headerStart[0]<<8)|headerStart[1];
	break;
      }
      }

      payloadDescriptionLength -= tlvLength;
      headerStart += tlvLength;
    }
    if (payloadDescriptionLength > 0) return False; // malformed TLV data
    headerStart += padding;
  }

  if (L) { // Sample-Specific info follows
    expectedHeaderSize += 4;
    if (packetSize < expectedHeaderSize) return False;

    unsigned ssInfoLength = (headerStart[2]<<8)|headerStart[3];
    headerStart += 4;

#ifdef DEBUG
    fprintf(stderr, "\tssInfoLength: %d\n", ssInfoLength);
#endif
    // Make sure "ssInfoLength" is valid
    if (ssInfoLength < 4) return False;
    expectedHeaderSize += (ssInfoLength - 4);
    unsigned nonPaddedSize = expectedHeaderSize;
    expectedHeaderSize += 3;
    expectedHeaderSize -= expectedHeaderSize%4; // adds padding
    if (packetSize < expectedHeaderSize) return False;
    unsigned char padding = expectedHeaderSize - nonPaddedSize;

    ssInfoLength -= 4;
    while (ssInfoLength > 3) {
      unsigned short tlvLength = (headerStart[0]<<8)|headerStart[1];
#ifdef DEBUG
      unsigned short tlvType = (headerStart[2]<<8)|headerStart[3];
#endif
      ssInfoLength -= 4;
      if (tlvLength > ssInfoLength) return False; // bad TLV
#ifdef DEBUG
      fprintf(stderr, "\t\tTLV '%c%c', length %d, leaving %d remaining bytes\n", tlvType>>8, tlvType&0xFF, tlvLength, ssInfoLength - tlvLength);
      for (int i = 0; i < tlvLength; ++i) fprintf(stderr, "%02x:", headerStart[4+i]); fprintf(stderr, "\n");
#endif
      ssInfoLength -= tlvLength;
      headerStart += 4 + tlvLength;
    }
    if (ssInfoLength > 0) return False; // malformed TLV data
    headerStart += padding;
  }

  fCurrentPacketBeginsFrame = fCurrentPacketCompletesFrame;
          // whether the *previous* packet ended a frame
  fCurrentPacketCompletesFrame = packet->rtpMarkerBit();

  resultSpecialHeaderSize = expectedHeaderSize;
#ifdef DEBUG
  fprintf(stderr, "Result special header size: %d\n", resultSpecialHeaderSize);
#endif
  return True;
}

char const* QuickTimeGenericRTPSource::MIMEtype() const {
  if (fMIMEtypeString == NULL) return MultiFramedRTPSource::MIMEtype();

  return fMIMEtypeString;
}


////////// QTGenericBufferedPacket and QTGenericBufferedPacketFactory impl

QTGenericBufferedPacket
::QTGenericBufferedPacket(QuickTimeGenericRTPSource& ourSource)
  : fOurSource(ourSource) {
}

QTGenericBufferedPacket::~QTGenericBufferedPacket() {
}

unsigned QTGenericBufferedPacket::
  nextEnclosedFrameSize(unsigned char*& framePtr, unsigned dataSize) {
  // We use the entire packet for a frame, unless "PCK" == 2
  if (fOurSource.qtState.PCK != 2) return dataSize;

  if (dataSize < 8) return 0; // sanity check

  unsigned short sampleLength = (framePtr[2]<<8)|framePtr[3];
  // later, extract and use the "timestamp" field #####
  framePtr += 8;
  dataSize -= 8;

  return sampleLength < dataSize ? sampleLength : dataSize;
}

BufferedPacket* QTGenericBufferedPacketFactory
::createNewPacket(MultiFramedRTPSource* ourSource) {
  return new QTGenericBufferedPacket((QuickTimeGenericRTPSource&)(*ourSource));
}
