// Provides generic TCP socket (buffered) I/O
// ***************************************************************************
+#include "api/internal/io/ByteArray_p.h"
#include "api/internal/io/TcpSocket_p.h"
#include "api/internal/io/TcpSocketEngine_p.h"
using namespace BamTools;
using namespace BamTools::Internal;
+#include <iostream> // debug
+
+#include <algorithm>
#include <sstream>
#include <vector>
using namespace std;
namespace Internal {
// constants
-static const size_t DEFAULT_BUFFER_SIZE = 0x8000;
+static const size_t DEFAULT_BUFFER_SIZE = 0x4000;
} // namespace Internal
} // namespace BamTools
{
// skip if we're already connected
if ( m_state == TcpSocket::ConnectedState ) {
- m_error = TcpSocket::SocketResourceError;
+ m_error = TcpSocket::SocketResourceError;
+ m_errorString = "socket already connected";
return false;
}
// reset socket state
- m_mode = mode;
- m_hostName = hostInfo.HostName();
- m_state = TcpSocket::UnconnectedState;
- m_error = TcpSocket::UnknownSocketError;
+ m_hostName = hostInfo.HostName();
+ m_mode = mode;
+ m_state = TcpSocket::UnconnectedState;
+ m_error = TcpSocket::UnknownSocketError;
// m_localPort = 0;
m_remotePort = 0;
// m_localAddress.Clear();
vector<HostAddress> addresses = hostInfo.Addresses();
if ( addresses.empty() ) {
m_error = TcpSocket::HostNotFoundError;
+ m_errorString = "no IP addresses found for host";
return false;
}
// if we get here, no connection could be made
m_error = TcpSocket::HostNotFoundError;
+ m_errorString = "could not connect to any host addresses";
return false;
}
// if host name was IP address ("x.x.x.x" or IPv6 format)
// otherwise host name was 'plain-text' ("www.foo.bar")
// we need to look up IP address(es)
- if ( hostAddress.HasIPAddress() )
+ if ( hostAddress.HasIPAddress() )
info.SetAddresses( vector<HostAddress>(1, hostAddress) );
else
info = HostInfo::Lookup(hostName, port);
m_remoteAddress.Clear();
m_hostName.clear();
m_cachedSocketDescriptor = -1;
+
+ // for future, make sure there's outgoing data that needs to be flushed
+ m_readBuffer.Clear();
}
TcpSocket::SocketError TcpSocket::GetError(void) const {
bool TcpSocket::ReadFromSocket(void) {
+ // check for any socket engine errors
+ if ( !m_engine->IsValid() ) {
+ m_errorString = "TcpSocket::ReadFromSocket - socket disconnected";
+ ResetSocketEngine();
+ return false;
+ }
+
// wait for ready read
bool timedOut;
bool isReadyRead = m_engine->WaitForRead(5000, &timedOut);
// if we simply timed out
if ( timedOut ) {
- // TODO: set error string
+ m_errorString = "TcpSocket::ReadFromSocket - timed out waiting for ready read";
+ // get error from engine ?
return false;
}
// otherwise, there was an error
else {
- // TODO: set error string
+ m_errorString = "TcpSocket::ReadFromSocket - encountered error while waiting for ready read";
+ // get error from engine ?
return false;
}
}
// #########################################################################
// clean this up - smells funky, but it's a key step so it has to be right
// #########################################################################
+
// get number of bytes available from socket
// (if 0, still try to read some data so we don't trigger any OS event behavior
// that respond to repeated access to a remote closed socket)
int64_t bytesToRead = m_engine->NumBytesAvailable();
- if ( bytesToRead < 0 )
+ if ( bytesToRead < 0 ) {
+ m_errorString = "TcpSocket::ReadFromSocket - encountered error while determining numBytesAvailable";
+ // get error from engine ?
return false;
+ }
else if ( bytesToRead == 0 )
bytesToRead = 4096;
char* buffer = m_readBuffer.Reserve(bytesToRead);
int64_t numBytesRead = m_engine->Read(buffer, bytesToRead);
- // (Qt uses -2 for no data, not error)
- // squeeze buffer back down & return success
- if ( numBytesRead == -2 ) {
- m_readBuffer.Chop(bytesToRead);
- return true;
- }
- // #########################################################################
-
- // check for any socket engine errors
- if ( !m_engine->IsValid() ) {
- // TODO: set error string
- ResetSocketEngine();
+ // if error while reading
+ if ( numBytesRead == -1 ) {
+ m_errorString = "TcpSocket::ReadFromSocket - encountered error while reading bytes";
+ // get error from engine ?
return false;
}
+ // handle special case (no data, but not error)
+ if ( numBytesRead == -2 )
+ m_readBuffer.Chop(bytesToRead);
+
// return success
return true;
}
-string TcpSocket::ReadLine(void) {
- if ( m_readBuffer.CanReadLine() )
- return m_readBuffer.ReadLine();
- return string();
+string TcpSocket::ReadLine(int64_t max) {
+
+ // prep result byte buffer
+ ByteArray result;
+
+ size_t bufferMax = ((max > static_cast<int64_t>(string::npos)) ? string::npos : static_cast<size_t>(max));
+ result.Resize(bufferMax);
+
+ // read data
+ int64_t readBytes(0);
+ if ( result.Size() == 0 ) {
+
+ if ( bufferMax == 0 )
+ bufferMax = string::npos;
+
+ result.Resize(1);
+
+ int64_t readResult;
+ do {
+ result.Resize( static_cast<size_t>(std::min(bufferMax, result.Size() + DEFAULT_BUFFER_SIZE)) );
+ readResult = ReadLine(result.Data()+readBytes, result.Size()-readBytes);
+ if ( readResult > 0 || readBytes == 0 )
+ readBytes += readResult;
+ } while ( readResult == DEFAULT_BUFFER_SIZE && result[static_cast<size_t>(readBytes-1)] != '\n' );
+
+ } else
+ readBytes = ReadLine(result.Data(), result.Size());
+
+ // clean up byte buffer
+ if ( readBytes <= 0 )
+ result.Clear();
+ else
+ result.Resize(static_cast<size_t>(readBytes));
+
+ // return byte buffer as string
+ return string( result.ConstData(), result.Size() );
+}
+
+int64_t TcpSocket::ReadLine(char* dest, size_t max) {
+
+ // wait for buffer to contain line contents
+ if ( !WaitForReadLine() ) {
+ m_errorString = "TcpSocket::ReadLine - error waiting for read line";
+ return -1;
+ }
+
+ // leave room for null term
+ if ( max < 2 )
+ return -1;
+ --max;
+
+ // read from buffer, handle newlines
+ int64_t readSoFar = m_readBuffer.ReadLine(dest, max);
+ if ( readSoFar && dest[readSoFar-1] == '\n' ) {
+
+ // adjust for windows-style '\r\n'
+ if ( readSoFar > 1 && dest[readSoFar-2] == '\r') {
+ --readSoFar;
+ dest[readSoFar-1] = '\n';
+ }
+ }
+
+ // null terminate & return number of bytes read
+ dest[readSoFar] = '\0';
+ return readSoFar;
}
void TcpSocket::ResetSocketEngine(void) {
m_cachedSocketDescriptor = -1;
}
+bool TcpSocket::WaitForReadLine(void) {
+
+ // wait until we can read a line (will return immediately if already capable)
+ while ( !CanReadLine() ) {
+ if ( !ReadFromSocket() )
+ return false;
+ }
+
+ // if we get here, success
+ return true;
+}
+
int64_t TcpSocket::Write(const char* data, const unsigned int numBytes) {
// single-shot attempt at write (not buffered, just try to shove the data through socket)