2018-02-26 19:35:16 -05:00
|
|
|
/*
|
|
|
|
|
2018-02-27 17:05:46 -05:00
|
|
|
This file is a part of JRTPLIB
|
|
|
|
Copyright (c) 1999-2017 Jori Liesenborgs
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 17:05:46 -05:00
|
|
|
Contact: jori.liesenborgs@gmail.com
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 17:05:46 -05:00
|
|
|
This library was developed at the Expertise Centre for Digital Media
|
|
|
|
(http://www.edm.uhasselt.be), a research center of the Hasselt University
|
|
|
|
(http://www.uhasselt.be). The library is based upon work done for
|
|
|
|
my thesis at the School for Knowledge Technology (Belgium/The Netherlands).
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 17:05:46 -05:00
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a
|
|
|
|
copy of this software and associated documentation files (the "Software"),
|
|
|
|
to deal in the Software without restriction, including without limitation
|
|
|
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
|
|
and/or sell copies of the Software, and to permit persons to whom the
|
|
|
|
Software is furnished to do so, subject to the following conditions:
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 17:05:46 -05:00
|
|
|
The above copyright notice and this permission notice shall be included
|
|
|
|
in all copies or substantial portions of the Software.
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 17:05:46 -05:00
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
|
|
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
|
|
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
|
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
|
|
IN THE SOFTWARE.
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 17:05:46 -05:00
|
|
|
*/
|
2018-02-26 19:35:16 -05:00
|
|
|
|
|
|
|
#include "rtptcptransmitter.h"
|
|
|
|
#include "rtprawpacket.h"
|
|
|
|
#include "rtptcpaddress.h"
|
|
|
|
#include "rtptimeutilities.h"
|
|
|
|
#include "rtpdefines.h"
|
|
|
|
#include "rtpstructs.h"
|
|
|
|
#include "rtpsocketutilinternal.h"
|
|
|
|
#include "rtpinternalutils.h"
|
|
|
|
#include "rtpselect.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <assert.h>
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
2018-02-27 17:05:46 -05:00
|
|
|
#define RTPTCPTRANS_MAXPACKSIZE 65535
|
2018-02-26 19:35:16 -05:00
|
|
|
|
|
|
|
namespace qrtplib
|
|
|
|
{
|
|
|
|
|
2018-02-27 17:05:46 -05:00
|
|
|
RTPTCPTransmitter::RTPTCPTransmitter()
|
2018-02-26 19:35:16 -05:00
|
|
|
{
|
2018-02-27 17:05:46 -05:00
|
|
|
m_created = false;
|
|
|
|
m_init = false;
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
RTPTCPTransmitter::~RTPTCPTransmitter()
|
|
|
|
{
|
2018-02-27 17:05:46 -05:00
|
|
|
Destroy();
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
int RTPTCPTransmitter::Init(bool tsafe)
|
|
|
|
{
|
2018-02-27 17:05:46 -05:00
|
|
|
if (m_init)
|
|
|
|
return ERR_RTP_TCPTRANS_ALREADYINIT;
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 17:05:46 -05:00
|
|
|
if (tsafe)
|
|
|
|
return ERR_RTP_NOTHREADSUPPORT;
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 17:05:46 -05:00
|
|
|
m_maxPackSize = RTPTCPTRANS_MAXPACKSIZE;
|
|
|
|
m_init = true;
|
|
|
|
return 0;
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
2018-02-27 17:54:23 -05:00
|
|
|
int RTPTCPTransmitter::Create(size_t maximumpacketsize __attribute__((unused)), const RTPTransmissionParams *transparams)
|
2018-02-26 19:35:16 -05:00
|
|
|
{
|
2018-02-27 17:05:46 -05:00
|
|
|
const RTPTCPTransmissionParams *params, defaultparams;
|
|
|
|
int status;
|
|
|
|
|
|
|
|
if (!m_init)
|
|
|
|
return ERR_RTP_TCPTRANS_NOTINIT;
|
|
|
|
|
|
|
|
if (m_created)
|
|
|
|
{
|
|
|
|
return ERR_RTP_TCPTRANS_ALREADYCREATED;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Obtain transmission parameters
|
|
|
|
|
|
|
|
if (transparams == 0)
|
|
|
|
params = &defaultparams;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (transparams->GetTransmissionProtocol() != RTPTransmitter::TCPProto)
|
|
|
|
{
|
|
|
|
return ERR_RTP_TCPTRANS_ILLEGALPARAMETERS;
|
|
|
|
}
|
|
|
|
params = static_cast<const RTPTCPTransmissionParams *>(transparams);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!params->GetCreatedAbortDescriptors())
|
|
|
|
{
|
|
|
|
if ((status = m_abortDesc.Init()) < 0)
|
|
|
|
{
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
m_pAbortDesc = &m_abortDesc;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
m_pAbortDesc = params->GetCreatedAbortDescriptors();
|
|
|
|
if (!m_pAbortDesc->IsInitialized())
|
|
|
|
{
|
|
|
|
return ERR_RTP_ABORTDESC_NOTINIT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
m_waitingForData = false;
|
|
|
|
m_created = true;
|
|
|
|
return 0;
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void RTPTCPTransmitter::Destroy()
|
|
|
|
{
|
2018-02-27 17:05:46 -05:00
|
|
|
if (!m_init)
|
|
|
|
return;
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 17:05:46 -05:00
|
|
|
if (!m_created)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 17:05:46 -05:00
|
|
|
ClearDestSockets();
|
|
|
|
FlushPackets();
|
|
|
|
m_created = false;
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 17:05:46 -05:00
|
|
|
if (m_waitingForData)
|
|
|
|
{
|
|
|
|
m_pAbortDesc->SendAbortSignal();
|
|
|
|
m_abortDesc.Destroy(); // Doesn't do anything if not initialized
|
2018-02-27 18:19:19 -05:00
|
|
|
// to make sure that the WaitForIncomingData function ended
|
|
|
|
|
|
|
|
}
|
|
|
|
else
|
|
|
|
m_abortDesc.Destroy(); // Doesn't do anything if not initialized
|
2018-02-26 19:35:16 -05:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
RTPTransmissionInfo *RTPTCPTransmitter::GetTransmissionInfo()
|
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
if (!m_init)
|
|
|
|
return 0;
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
RTPTransmissionInfo *tinf = new RTPTCPTransmissionInfo();
|
|
|
|
return tinf;
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void RTPTCPTransmitter::DeleteTransmissionInfo(RTPTransmissionInfo *i)
|
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
if (!m_init)
|
|
|
|
return;
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
delete i;
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
2018-02-27 17:05:46 -05:00
|
|
|
int RTPTCPTransmitter::GetLocalHostName(uint8_t *buffer, size_t *bufferlength)
|
2018-02-26 19:35:16 -05:00
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
if (!m_init)
|
|
|
|
return ERR_RTP_TCPTRANS_NOTINIT;
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
if (!m_created)
|
|
|
|
{
|
|
|
|
return ERR_RTP_TCPTRANS_NOTCREATED;
|
|
|
|
}
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
if (m_localHostname.size() == 0)
|
|
|
|
{
|
2018-02-27 17:05:46 -05:00
|
|
|
//
|
|
|
|
// TODO
|
|
|
|
// TODO
|
|
|
|
// TODO
|
|
|
|
// TODO
|
|
|
|
//
|
2018-02-27 18:19:19 -05:00
|
|
|
m_localHostname.resize(9);
|
|
|
|
memcpy(&m_localHostname[0], "localhost", m_localHostname.size());
|
|
|
|
}
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
if ((*bufferlength) < m_localHostname.size())
|
|
|
|
{
|
|
|
|
*bufferlength = m_localHostname.size(); // tell the application the required size of the buffer
|
|
|
|
return ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL;
|
|
|
|
}
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
memcpy(buffer, &m_localHostname[0], m_localHostname.size());
|
|
|
|
*bufferlength = m_localHostname.size();
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
return 0;
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
bool RTPTCPTransmitter::ComesFromThisTransmitter(const RTPAddress *addr)
|
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
if (!m_init)
|
|
|
|
return false;
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
if (addr == 0)
|
|
|
|
return false;
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
if (!m_created)
|
|
|
|
return false;
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
if (addr->GetAddressType() != RTPAddress::TCPAddress)
|
|
|
|
return false;
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
bool v = false;
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
// TODO: for now, we're assuming that we can't just send to the same transmitter
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
return v;
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
int RTPTCPTransmitter::Poll()
|
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
if (!m_init)
|
|
|
|
return ERR_RTP_TCPTRANS_NOTINIT;
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
if (!m_created)
|
|
|
|
{
|
|
|
|
return ERR_RTP_TCPTRANS_NOTCREATED;
|
|
|
|
}
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
std::map<SocketType, SocketData>::iterator it = m_destSockets.begin();
|
|
|
|
std::map<SocketType, SocketData>::iterator end = m_destSockets.end();
|
|
|
|
int status = 0;
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
vector<SocketType> errSockets;
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
while (it != end)
|
2018-02-27 17:05:46 -05:00
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
SocketType sock = it->first;
|
|
|
|
status = PollSocket(sock, it->second);
|
|
|
|
if (status < 0)
|
|
|
|
{
|
|
|
|
// Stop immediately on out of memory
|
|
|
|
if (status == ERR_RTP_OUTOFMEM)
|
|
|
|
break;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
errSockets.push_back(sock);
|
|
|
|
// Don't let this count as an error (due to a closed connection for example),
|
|
|
|
// otherwise the poll thread (if used) will stop because of this. Since there
|
|
|
|
// may be more than one connection, that's not desirable in general.
|
|
|
|
status = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
++it;
|
2018-02-27 17:05:46 -05:00
|
|
|
}
|
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
for (size_t i = 0; i < errSockets.size(); i++)
|
|
|
|
OnReceiveError(errSockets[i]);
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
return status;
|
2018-02-27 17:05:46 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
int RTPTCPTransmitter::WaitForIncomingData(const RTPTime &delay, bool *dataavailable)
|
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
if (!m_init)
|
|
|
|
return ERR_RTP_TCPTRANS_NOTINIT;
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
if (!m_created)
|
|
|
|
{
|
|
|
|
return ERR_RTP_TCPTRANS_NOTCREATED;
|
|
|
|
}
|
|
|
|
if (m_waitingForData)
|
|
|
|
{
|
|
|
|
return ERR_RTP_TCPTRANS_ALREADYWAITING;
|
|
|
|
}
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
m_tmpSocks.resize(m_destSockets.size() + 1);
|
|
|
|
m_tmpFlags.resize(m_tmpSocks.size());
|
|
|
|
SocketType abortSocket = m_pAbortDesc->GetAbortSocket();
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
std::map<SocketType, SocketData>::iterator it = m_destSockets.begin();
|
|
|
|
std::map<SocketType, SocketData>::iterator end = m_destSockets.end();
|
|
|
|
int idx = 0;
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
while (it != end)
|
|
|
|
{
|
|
|
|
m_tmpSocks[idx] = it->first;
|
|
|
|
m_tmpFlags[idx] = 0;
|
|
|
|
++it;
|
|
|
|
idx++;
|
|
|
|
}
|
|
|
|
m_tmpSocks[idx] = abortSocket;
|
|
|
|
m_tmpFlags[idx] = 0;
|
|
|
|
int idxAbort = idx;
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
m_waitingForData = true;
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
//cout << "Waiting for " << delay.GetDouble() << " seconds for data on " << m_tmpSocks.size() << " sockets" << endl;
|
|
|
|
int status = RTPSelect(&m_tmpSocks[0], &m_tmpFlags[0], m_tmpSocks.size(), delay);
|
|
|
|
if (status < 0)
|
|
|
|
{
|
|
|
|
m_waitingForData = false;
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
return status;
|
|
|
|
}
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
m_waitingForData = false;
|
|
|
|
if (!m_created) // destroy called
|
|
|
|
{
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
return 0;
|
|
|
|
}
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
// if aborted, read from abort buffer
|
|
|
|
if (m_tmpFlags[idxAbort])
|
|
|
|
m_pAbortDesc->ReadSignallingByte();
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
if (dataavailable != 0)
|
2018-02-27 17:05:46 -05:00
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
bool avail = false;
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
for (size_t i = 0; i < m_tmpFlags.size(); i++)
|
|
|
|
{
|
|
|
|
if (m_tmpFlags[i])
|
|
|
|
{
|
|
|
|
avail = true;
|
|
|
|
//cout << "Data available!" << endl;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (avail)
|
|
|
|
*dataavailable = true;
|
|
|
|
else
|
|
|
|
*dataavailable = false;
|
|
|
|
}
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
return 0;
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
int RTPTCPTransmitter::AbortWait()
|
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
if (!m_init)
|
|
|
|
return ERR_RTP_TCPTRANS_NOTINIT;
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
if (!m_created)
|
|
|
|
{
|
|
|
|
return ERR_RTP_TCPTRANS_NOTCREATED;
|
|
|
|
}
|
|
|
|
if (!m_waitingForData)
|
|
|
|
{
|
|
|
|
return ERR_RTP_TCPTRANS_NOTWAITING;
|
|
|
|
}
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
m_pAbortDesc->SendAbortSignal();
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
return 0;
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
2018-02-27 17:05:46 -05:00
|
|
|
int RTPTCPTransmitter::SendRTPData(const void *data, size_t len)
|
2018-02-26 19:35:16 -05:00
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
return SendRTPRTCPData(data, len);
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
2018-02-27 17:05:46 -05:00
|
|
|
int RTPTCPTransmitter::SendRTCPData(const void *data, size_t len)
|
2018-02-26 19:35:16 -05:00
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
return SendRTPRTCPData(data, len);
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
int RTPTCPTransmitter::AddDestination(const RTPAddress &addr)
|
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
if (!m_init)
|
|
|
|
return ERR_RTP_TCPTRANS_NOTINIT;
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
if (!m_created)
|
|
|
|
{
|
|
|
|
return ERR_RTP_TCPTRANS_NOTCREATED;
|
|
|
|
}
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
if (addr.GetAddressType() != RTPAddress::TCPAddress)
|
|
|
|
{
|
|
|
|
return ERR_RTP_TCPTRANS_INVALIDADDRESSTYPE;
|
|
|
|
}
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
const RTPTCPAddress &a = static_cast<const RTPTCPAddress &>(addr);
|
|
|
|
SocketType s = a.GetSocket();
|
|
|
|
if (s == 0)
|
|
|
|
{
|
|
|
|
return ERR_RTP_TCPTRANS_NOSOCKETSPECIFIED;
|
|
|
|
}
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
int status = ValidateSocket(s);
|
|
|
|
if (status != 0)
|
|
|
|
{
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
return status;
|
|
|
|
}
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
std::map<SocketType, SocketData>::iterator it = m_destSockets.find(s);
|
|
|
|
if (it != m_destSockets.end())
|
|
|
|
{
|
|
|
|
|
|
|
|
return ERR_RTP_TCPTRANS_SOCKETALREADYINDESTINATIONS;
|
|
|
|
}
|
|
|
|
m_destSockets[s] = SocketData();
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
// Because the sockets are also used for incoming data, we'll abort a wait
|
|
|
|
// that may be in progress, otherwise it could take a few seconds until the
|
|
|
|
// new socket is monitored for incoming data
|
|
|
|
m_pAbortDesc->SendAbortSignal();
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
return 0;
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
int RTPTCPTransmitter::DeleteDestination(const RTPAddress &addr)
|
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
if (!m_init)
|
|
|
|
return ERR_RTP_TCPTRANS_NOTINIT;
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
if (!m_created)
|
|
|
|
{
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
return ERR_RTP_TCPTRANS_NOTCREATED;
|
|
|
|
}
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
if (addr.GetAddressType() != RTPAddress::TCPAddress)
|
|
|
|
{
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
return ERR_RTP_TCPTRANS_INVALIDADDRESSTYPE;
|
|
|
|
}
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
const RTPTCPAddress &a = static_cast<const RTPTCPAddress &>(addr);
|
|
|
|
SocketType s = a.GetSocket();
|
|
|
|
if (s == 0)
|
|
|
|
{
|
|
|
|
|
|
|
|
return ERR_RTP_TCPTRANS_NOSOCKETSPECIFIED;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::map<SocketType, SocketData>::iterator it = m_destSockets.find(s);
|
|
|
|
if (it == m_destSockets.end())
|
|
|
|
{
|
|
|
|
|
|
|
|
return ERR_RTP_TCPTRANS_SOCKETNOTFOUNDINDESTINATIONS;
|
|
|
|
}
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
// Clean up possibly allocated memory
|
|
|
|
uint8_t *pBuf = it->second.ExtractDataBuffer();
|
|
|
|
if (pBuf)
|
|
|
|
delete[] pBuf;
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
m_destSockets.erase(it);
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
return 0;
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void RTPTCPTransmitter::ClearDestinations()
|
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
if (!m_init)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (m_created)
|
|
|
|
ClearDestSockets();
|
2018-02-26 19:35:16 -05:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
bool RTPTCPTransmitter::SupportsMulticasting()
|
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
return false;
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
int RTPTCPTransmitter::JoinMulticastGroup(const RTPAddress &)
|
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
return ERR_RTP_TCPTRANS_NOMULTICASTSUPPORT;
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
int RTPTCPTransmitter::LeaveMulticastGroup(const RTPAddress &)
|
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
return ERR_RTP_TCPTRANS_NOMULTICASTSUPPORT;
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void RTPTCPTransmitter::LeaveAllMulticastGroups()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
int RTPTCPTransmitter::SetReceiveMode(RTPTransmitter::ReceiveMode m)
|
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
if (m != RTPTransmitter::AcceptAll)
|
|
|
|
return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED;
|
|
|
|
return 0;
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
int RTPTCPTransmitter::AddToIgnoreList(const RTPAddress &)
|
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED;
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
int RTPTCPTransmitter::DeleteFromIgnoreList(const RTPAddress &)
|
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED;
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void RTPTCPTransmitter::ClearIgnoreList()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
int RTPTCPTransmitter::AddToAcceptList(const RTPAddress &)
|
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED;
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
int RTPTCPTransmitter::DeleteFromAcceptList(const RTPAddress &)
|
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
return ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED;
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void RTPTCPTransmitter::ClearAcceptList()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
int RTPTCPTransmitter::SetMaximumPacketSize(size_t s)
|
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
if (!m_init)
|
|
|
|
return ERR_RTP_TCPTRANS_NOTINIT;
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
if (!m_created)
|
|
|
|
{
|
|
|
|
|
|
|
|
return ERR_RTP_TCPTRANS_NOTCREATED;
|
|
|
|
}
|
|
|
|
if (s > RTPTCPTRANS_MAXPACKSIZE)
|
|
|
|
{
|
|
|
|
|
|
|
|
return ERR_RTP_TCPTRANS_SPECIFIEDSIZETOOBIG;
|
|
|
|
}
|
|
|
|
m_maxPackSize = s;
|
|
|
|
|
|
|
|
return 0;
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
bool RTPTCPTransmitter::NewDataAvailable()
|
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
if (!m_init)
|
|
|
|
return false;
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
bool v;
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
if (!m_created)
|
|
|
|
v = false;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (m_rawpacketlist.empty())
|
|
|
|
v = false;
|
|
|
|
else
|
|
|
|
v = true;
|
|
|
|
}
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
return v;
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
RTPRawPacket *RTPTCPTransmitter::GetNextPacket()
|
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
if (!m_init)
|
|
|
|
return 0;
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
RTPRawPacket *p;
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
if (!m_created)
|
|
|
|
{
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (m_rawpacketlist.empty())
|
|
|
|
{
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
p = *(m_rawpacketlist.begin());
|
|
|
|
m_rawpacketlist.pop_front();
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
return p;
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Here the private functions start...
|
|
|
|
|
|
|
|
void RTPTCPTransmitter::FlushPackets()
|
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
std::list<RTPRawPacket*>::const_iterator it;
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
for (it = m_rawpacketlist.begin(); it != m_rawpacketlist.end(); ++it)
|
|
|
|
delete *it;
|
|
|
|
m_rawpacketlist.clear();
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
int RTPTCPTransmitter::PollSocket(SocketType sock, SocketData &sdata)
|
|
|
|
{
|
|
|
|
#ifdef RTP_SOCKETTYPE_WINSOCK
|
2018-02-27 18:19:19 -05:00
|
|
|
unsigned long len;
|
2018-02-26 19:35:16 -05:00
|
|
|
#else
|
2018-02-27 18:19:19 -05:00
|
|
|
size_t len;
|
2018-02-26 19:35:16 -05:00
|
|
|
#endif // RTP_SOCKETTYPE_WINSOCK
|
2018-02-27 18:19:19 -05:00
|
|
|
bool dataavailable;
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
do
|
2018-02-27 17:05:46 -05:00
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
len = 0;
|
|
|
|
RTPIOCTL(sock, FIONREAD, &len);
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
if (len <= 0)
|
|
|
|
dataavailable = false;
|
|
|
|
else
|
|
|
|
dataavailable = true;
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
if (dataavailable)
|
2018-02-27 17:05:46 -05:00
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
RTPTime curtime = RTPTime::CurrentTime();
|
|
|
|
int relevantLen = RTPTCPTRANS_MAXPACKSIZE + 2;
|
|
|
|
|
|
|
|
if ((int) len < relevantLen)
|
|
|
|
relevantLen = (int) len;
|
|
|
|
|
|
|
|
bool complete = false;
|
|
|
|
int status = sdata.ProcessAvailableBytes(sock, relevantLen, complete);
|
|
|
|
if (status < 0)
|
|
|
|
return status;
|
|
|
|
|
|
|
|
if (complete)
|
|
|
|
{
|
|
|
|
uint8_t *pBuf = sdata.ExtractDataBuffer();
|
|
|
|
if (pBuf)
|
|
|
|
{
|
|
|
|
int dataLength = sdata.m_dataLength;
|
|
|
|
sdata.Reset();
|
|
|
|
|
|
|
|
RTPTCPAddress *pAddr = new RTPTCPAddress(sock);
|
|
|
|
if (pAddr == 0)
|
|
|
|
return ERR_RTP_OUTOFMEM;
|
|
|
|
|
|
|
|
bool isrtp = true;
|
|
|
|
if (dataLength > (int) sizeof(RTCPCommonHeader))
|
|
|
|
{
|
|
|
|
RTCPCommonHeader *rtcpheader = (RTCPCommonHeader *) pBuf;
|
|
|
|
uint8_t packettype = rtcpheader->packettype;
|
|
|
|
|
|
|
|
if (packettype >= 200 && packettype <= 204)
|
|
|
|
isrtp = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
RTPRawPacket *pPack = new RTPRawPacket(pBuf, dataLength, pAddr, curtime, isrtp);
|
|
|
|
if (pPack == 0)
|
|
|
|
{
|
|
|
|
delete pAddr;
|
|
|
|
delete[] pBuf;
|
|
|
|
return ERR_RTP_OUTOFMEM;
|
|
|
|
}
|
|
|
|
m_rawpacketlist.push_back(pPack);
|
|
|
|
}
|
|
|
|
}
|
2018-02-27 17:05:46 -05:00
|
|
|
}
|
2018-02-27 18:19:19 -05:00
|
|
|
} while (dataavailable);
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
return 0;
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
int RTPTCPTransmitter::SendRTPRTCPData(const void *data, size_t len)
|
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
if (!m_init)
|
|
|
|
return ERR_RTP_TCPTRANS_NOTINIT;
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
if (!m_created)
|
|
|
|
{
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
return ERR_RTP_TCPTRANS_NOTCREATED;
|
|
|
|
}
|
|
|
|
if (len > RTPTCPTRANS_MAXPACKSIZE)
|
|
|
|
{
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
return ERR_RTP_TCPTRANS_SPECIFIEDSIZETOOBIG;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::map<SocketType, SocketData>::iterator it = m_destSockets.begin();
|
|
|
|
std::map<SocketType, SocketData>::iterator end = m_destSockets.end();
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
vector<SocketType> errSockets;
|
|
|
|
int flags = 0;
|
2018-02-26 19:35:16 -05:00
|
|
|
#ifdef RTP_HAVE_MSG_NOSIGNAL
|
2018-02-27 18:19:19 -05:00
|
|
|
flags = MSG_NOSIGNAL;
|
2018-02-26 19:35:16 -05:00
|
|
|
#endif // RTP_HAVE_MSG_NOSIGNAL
|
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
while (it != end)
|
|
|
|
{
|
|
|
|
uint8_t lengthBytes[2] =
|
|
|
|
{ (uint8_t) ((len >> 8) & 0xff), (uint8_t) (len & 0xff) };
|
|
|
|
SocketType sock = it->first;
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
if (send(sock, (const char *) lengthBytes, 2, flags) < 0 || send(sock, (const char *) data, len, flags) < 0)
|
|
|
|
errSockets.push_back(sock);
|
|
|
|
++it;
|
|
|
|
}
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
if (errSockets.size() != 0)
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < errSockets.size(); i++)
|
|
|
|
OnSendError(errSockets[i]);
|
|
|
|
}
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
// Don't return an error code to avoid the poll thread exiting
|
|
|
|
// due to one closed connection for example
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
return 0;
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
int RTPTCPTransmitter::ValidateSocket(SocketType)
|
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
// TODO: should we even do a check (for a TCP socket)?
|
|
|
|
return 0;
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void RTPTCPTransmitter::ClearDestSockets()
|
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
std::map<SocketType, SocketData>::iterator it = m_destSockets.begin();
|
|
|
|
std::map<SocketType, SocketData>::iterator end = m_destSockets.end();
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
while (it != end)
|
|
|
|
{
|
|
|
|
uint8_t *pBuf = it->second.ExtractDataBuffer();
|
|
|
|
if (pBuf)
|
|
|
|
delete[] pBuf;
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
++it;
|
|
|
|
}
|
|
|
|
m_destSockets.clear();
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
RTPTCPTransmitter::SocketData::SocketData()
|
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
Reset();
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void RTPTCPTransmitter::SocketData::Reset()
|
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
m_lengthBufferOffset = 0;
|
|
|
|
m_dataLength = 0;
|
|
|
|
m_dataBufferOffset = 0;
|
|
|
|
m_pDataBuffer = 0;
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
RTPTCPTransmitter::SocketData::~SocketData()
|
|
|
|
{
|
2018-02-27 18:19:19 -05:00
|
|
|
assert(m_pDataBuffer == 0); // Should be deleted externally to avoid storing a memory manager in the class
|
2018-02-26 19:35:16 -05:00
|
|
|
}
|
|
|
|
|
2018-02-27 17:05:46 -05:00
|
|
|
int RTPTCPTransmitter::SocketData::ProcessAvailableBytes(SocketType sock, int availLen, bool &complete)
|
|
|
|
{
|
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
const int numLengthBuffer = 2;
|
|
|
|
if (m_lengthBufferOffset < numLengthBuffer) // first we need to get the length
|
|
|
|
{
|
|
|
|
assert(m_pDataBuffer == 0);
|
|
|
|
int num = numLengthBuffer - m_lengthBufferOffset;
|
|
|
|
if (num > availLen)
|
|
|
|
num = availLen;
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
int r = 0;
|
|
|
|
if (num > 0)
|
|
|
|
{
|
|
|
|
r = (int) recv(sock, (char *) (m_lengthBuffer + m_lengthBufferOffset), num, 0);
|
|
|
|
if (r < 0)
|
|
|
|
return ERR_RTP_TCPTRANS_ERRORINRECV;
|
|
|
|
}
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
m_lengthBufferOffset += r;
|
|
|
|
availLen -= r;
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
assert(m_lengthBufferOffset <= numLengthBuffer);
|
|
|
|
if (m_lengthBufferOffset == numLengthBuffer) // we can constuct a length
|
|
|
|
{
|
|
|
|
int l = 0;
|
|
|
|
for (int i = numLengthBuffer - 1, shift = 0; i >= 0; i--, shift += 8)
|
|
|
|
l |= ((int) m_lengthBuffer[i]) << shift;
|
2018-02-26 19:35:16 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
m_dataLength = l;
|
|
|
|
m_dataBufferOffset = 0;
|
2018-02-27 17:05:46 -05:00
|
|
|
|
|
|
|
//cout << "Expecting " << m_dataLength << " bytes" << endl;
|
|
|
|
|
|
|
|
// avoid allocation of length 0
|
2018-02-27 18:19:19 -05:00
|
|
|
if (l == 0)
|
|
|
|
l = 1;
|
2018-02-27 17:05:46 -05:00
|
|
|
|
|
|
|
// We don't yet know if it's an RTP or RTCP packet, so we'll stick to RTP
|
2018-02-27 18:19:19 -05:00
|
|
|
m_pDataBuffer = new uint8_t[l];
|
|
|
|
if (m_pDataBuffer == 0)
|
|
|
|
return ERR_RTP_OUTOFMEM;
|
|
|
|
}
|
|
|
|
}
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
if (m_lengthBufferOffset == numLengthBuffer && m_pDataBuffer) // the last one is to make sure we didn't run out of memory
|
|
|
|
{
|
|
|
|
if (m_dataBufferOffset < m_dataLength)
|
|
|
|
{
|
|
|
|
int num = m_dataLength - m_dataBufferOffset;
|
|
|
|
if (num > availLen)
|
|
|
|
num = availLen;
|
|
|
|
|
|
|
|
int r = 0;
|
|
|
|
if (num > 0)
|
|
|
|
{
|
|
|
|
r = (int) recv(sock, (char *) (m_pDataBuffer + m_dataBufferOffset), num, 0);
|
|
|
|
if (r < 0)
|
|
|
|
return ERR_RTP_TCPTRANS_ERRORINRECV;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_dataBufferOffset += r;
|
|
|
|
availLen -= r;
|
|
|
|
}
|
2018-02-27 17:05:46 -05:00
|
|
|
|
2018-02-27 18:19:19 -05:00
|
|
|
if (m_dataBufferOffset == m_dataLength)
|
|
|
|
complete = true;
|
|
|
|
}
|
|
|
|
return 0;
|
2018-02-27 17:05:46 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
} // end namespace
|
2018-02-26 19:35:16 -05:00
|
|
|
|