1 // ***************************************************************************
2 // TcpSocket_p.cpp (c) 2011 Derek Barnett
3 // Marth Lab, Department of Biology, Boston College
4 // ---------------------------------------------------------------------------
5 // Last modified: 5 January 2012 (DB)
6 // ---------------------------------------------------------------------------
7 // Provides basic TCP I/O interface
8 // ***************************************************************************
10 #include "api/internal/io/ByteArray_p.h"
11 #include "api/internal/io/TcpSocket_p.h"
12 #include "api/internal/io/TcpSocketEngine_p.h"
13 using namespace BamTools;
14 using namespace BamTools::Internal;
22 // ------------------------------------
23 // static utility methods & constants
24 // ------------------------------------
30 static const size_t DEFAULT_BUFFER_SIZE = 0x4000;
32 } // namespace Internal
33 } // namespace BamTools
35 // --------------------------
36 // TcpSocket implementation
37 // --------------------------
39 TcpSocket::TcpSocket(void)
40 : m_mode(IBamIODevice::NotOpen)
44 , m_cachedSocketDescriptor(-1)
45 , m_readBuffer(DEFAULT_BUFFER_SIZE)
46 , m_error(TcpSocket::UnknownSocketError)
47 , m_state(TcpSocket::UnconnectedState)
50 TcpSocket::~TcpSocket(void) {
51 if ( m_state == TcpSocket::ConnectedState )
55 size_t TcpSocket::BufferBytesAvailable(void) const {
56 return m_readBuffer.Size();
59 bool TcpSocket::CanReadLine(void) const {
60 return m_readBuffer.CanReadLine();
63 void TcpSocket::ClearBuffer(void) {
67 bool TcpSocket::ConnectImpl(const HostInfo& hostInfo,
68 const std::string& port,
69 IBamIODevice::OpenMode mode)
71 // skip if we're already connected
72 if ( m_state == TcpSocket::ConnectedState ) {
73 m_error = TcpSocket::SocketResourceError;
74 m_errorString = "socket already connected";
79 m_hostName = hostInfo.HostName();
81 m_state = TcpSocket::UnconnectedState;
82 m_error = TcpSocket::UnknownSocketError;
85 // m_localAddress.Clear();
86 m_remoteAddress.Clear();
89 // fetch candidate addresses for requested host
90 vector<HostAddress> addresses = hostInfo.Addresses();
91 if ( addresses.empty() ) {
92 m_error = TcpSocket::HostNotFoundError;
93 m_errorString = "no IP addresses found for host";
97 // convert port string to integer
98 stringstream ss(port);
99 uint16_t portNumber(0);
102 // iterate through adddresses
103 vector<HostAddress>::const_iterator addrIter = addresses.begin();
104 vector<HostAddress>::const_iterator addrEnd = addresses.end();
105 for ( ; addrIter != addrEnd; ++addrIter) {
106 const HostAddress& addr = (*addrIter);
108 // try to initialize socket engine with this address
109 if ( !InitializeSocketEngine(addr.GetProtocol()) ) {
110 // failure to initialize is OK here
111 // we'll just try the next available address
115 // attempt actual connection
116 if ( m_engine->Connect(addr, portNumber) ) {
118 // if connection successful, update our state & return true
120 // m_localAddress = m_engine->GetLocalAddress();
121 // m_localPort = m_engine->GetLocalPort();
122 m_remoteAddress = m_engine->GetRemoteAddress();
123 m_remotePort = m_engine->GetRemotePort();
124 m_cachedSocketDescriptor = m_engine->GetSocketDescriptor();
125 m_state = TcpSocket::ConnectedState;
130 // if we get here, no connection could be made
131 m_error = TcpSocket::HostNotFoundError;
132 m_errorString = "could not connect to any host addresses";
136 bool TcpSocket::ConnectToHost(const string& hostName,
138 IBamIODevice::OpenMode mode)
142 return ConnectToHost(hostName, ss.str(), mode);
146 bool TcpSocket::ConnectToHost(const string& hostName,
148 IBamIODevice::OpenMode mode)
150 // create new address object with requested host name
151 HostAddress hostAddress;
152 hostAddress.SetAddress(hostName);
155 // if host name was IP address ("x.x.x.x" or IPv6 format)
156 // otherwise host name was 'plain-text' ("www.foo.bar")
157 // we need to look up IP address(es)
158 if ( hostAddress.HasIPAddress() )
159 info.SetAddresses( vector<HostAddress>(1, hostAddress) );
161 info = HostInfo::Lookup(hostName, port);
163 // attempt connection on requested port
164 return ConnectImpl(info, port, mode);
167 void TcpSocket::DisconnectFromHost(void) {
169 // close socket engine & delete
170 if ( m_state == TcpSocket::ConnectedState )
173 // reset connection state
176 // m_localAddress.Clear();
177 m_remoteAddress.Clear();
179 m_cachedSocketDescriptor = -1;
181 // for future, make sure there's outgoing data that needs to be flushed
182 m_readBuffer.Clear();
185 TcpSocket::SocketError TcpSocket::GetError(void) const {
189 std::string TcpSocket::GetErrorString(void) const {
190 return m_errorString;
193 std::string TcpSocket::GetHostName(void) const {
197 //HostAddress TcpSocket::GetLocalAddress(void) const {
198 // return m_localAddress;
201 //uint16_t TcpSocket::GetLocalPort(void) const {
202 // return m_localPort;
205 HostAddress TcpSocket::GetRemoteAddress(void) const {
206 return m_remoteAddress;
209 uint16_t TcpSocket::GetRemotePort(void) const {
213 TcpSocket::SocketState TcpSocket::GetState(void) const {
217 bool TcpSocket::InitializeSocketEngine(HostAddress::NetworkProtocol protocol) {
219 m_engine = new TcpSocketEngine;
220 return m_engine->Initialize(protocol);
223 bool TcpSocket::IsConnected(void) const {
226 return ( m_engine->IsValid() && (m_state == TcpSocket::ConnectedState) );
229 // may be read in a look until desired data amount has been read
230 // returns: number of bytes read, or -1 if error
231 int64_t TcpSocket::Read(char* data, const unsigned int numBytes) {
233 // if we have data in buffer, just return it
234 if ( !m_readBuffer.IsEmpty() ) {
235 const size_t bytesRead = m_readBuffer.Read(data, numBytes);
236 return static_cast<int64_t>(bytesRead);
239 // otherwise, we'll need to fetch data from socket
240 // first make sure we have a valid socket engine
241 if ( m_engine == 0 ) {
242 // TODO: set error string/state?
246 // fetch data from socket, return 0 for success, -1 for failure
247 // since this should be called in a loop,
248 // we'll pull the actual bytes from the buffer on next iteration
249 const int64_t socketBytesRead = ReadFromSocket();
250 if ( socketBytesRead < 0 ) {
251 // TODO: set error string/state ?
255 // we should have data now in buffer, try to fetch requested amount
256 // if nothing in buffer, we will return 0 bytes read (signals EOF reached)
257 const size_t numBytesRead = m_readBuffer.Read(data, numBytes);
258 return static_cast<int64_t>(numBytesRead);
261 int64_t TcpSocket::ReadFromSocket(void) {
263 // check for any socket engine errors
264 if ( !m_engine->IsValid() ) {
265 m_errorString = "TcpSocket::ReadFromSocket - socket disconnected";
270 // wait for ready read
272 const bool isReadyRead = m_engine->WaitForRead(5000, &timedOut);
275 if ( !isReadyRead ) {
277 // if we simply timed out
279 // TODO: get add'l error info from engine ?
280 m_errorString = "TcpSocket::ReadFromSocket - timed out waiting for ready read";
283 // otherwise, there was some other error
285 // TODO: get add'l error info from engine ?
286 m_errorString = "TcpSocket::ReadFromSocket - encountered error while waiting for ready read";
293 // get number of bytes available from socket
294 const int64_t bytesToRead = m_engine->NumBytesAvailable();
295 if ( bytesToRead < 0 ) {
296 // TODO: get add'l error info from engine ?
297 m_errorString = "TcpSocket::ReadFromSocket - encountered error while determining numBytesAvailable";
301 // make space in buffer & read from socket
302 char* buffer = m_readBuffer.Reserve(bytesToRead);
303 const int64_t numBytesRead = m_engine->Read(buffer, bytesToRead);
304 if ( numBytesRead == -1 ) {
305 // TODO: get add'l error info from engine ?
306 m_errorString = "TcpSocket::ReadFromSocket - encountered error while reading bytes";
309 // return number of bytes actually read
313 string TcpSocket::ReadLine(int64_t max) {
315 // prep result byte buffer
317 size_t bufferMax = ((max > static_cast<int64_t>(UINT_MAX))
318 ? UINT_MAX : static_cast<size_t>(max));
319 result.Resize(bufferMax);
322 int64_t readBytes(0);
323 if ( result.Size() == 0 ) {
325 if ( bufferMax == 0 )
326 bufferMax = UINT_MAX;
332 result.Resize( static_cast<size_t>(min(bufferMax, result.Size() + DEFAULT_BUFFER_SIZE)) );
333 readResult = ReadLine(result.Data()+readBytes, result.Size()-readBytes);
334 if ( readResult > 0 || readBytes == 0 )
335 readBytes += readResult;
336 } while ( readResult == DEFAULT_BUFFER_SIZE && result[static_cast<size_t>(readBytes-1)] != '\n' );
339 readBytes = ReadLine(result.Data(), result.Size());
341 // clean up byte buffer
342 if ( readBytes <= 0 )
345 result.Resize(static_cast<size_t>(readBytes));
347 // return byte buffer as string
348 return string( result.ConstData(), result.Size() );
351 int64_t TcpSocket::ReadLine(char* dest, size_t max) {
353 // wait for buffer to contain line contents
354 if ( !WaitForReadLine() ) {
355 m_errorString = "TcpSocket::ReadLine - error waiting for read line";
359 // leave room for null term
364 // read from buffer, handle newlines
365 int64_t readSoFar = m_readBuffer.ReadLine(dest, max);
366 if ( readSoFar && dest[readSoFar-1] == '\n' ) {
368 // adjust for windows-style '\r\n'
369 if ( readSoFar > 1 && dest[readSoFar-2] == '\r') {
371 dest[readSoFar-1] = '\n';
375 // null terminate & return number of bytes read
376 dest[readSoFar] = '\0';
380 void TcpSocket::ResetSocketEngine(void) {
382 // shut down socket engine
389 // reset our state & cached socket handle
390 m_state = TcpSocket::UnconnectedState;
391 m_cachedSocketDescriptor = -1;
394 bool TcpSocket::WaitForReadLine(void) {
396 // wait until we can read a line (will return immediately if already capable)
397 while ( !CanReadLine() ) {
398 if ( !ReadFromSocket() )
402 // if we get here, success
406 int64_t TcpSocket::Write(const char* data, const unsigned int numBytes) {
408 // single-shot attempt at write (not buffered, just try to shove the data through socket)
409 // this method purely exists to send 'small' HTTP requests/FTP commands from client to server
411 // wait for our socket to be write-able
413 const bool isReadyWrite = m_engine->WaitForWrite(3000, &timedOut);
415 // if ready, return number of bytes written
417 return m_engine->Write(data, numBytes);
419 // otherwise, socket not ready for writing
420 // set error string depending on reason & return failure
422 // TODO: get add'l error info from engine ??
423 m_errorString = "TcpSocket::Write - timed out waiting for ready-write";
426 // TODO: get add'l error info from engine ??
427 m_errorString = "TcpSocket::Write - error encountered while waiting for ready-write";