]> git.donarmstrong.com Git - bamtools.git/blob - src/api/internal/io/TcpSocket_p.cpp
Fixed EOF issues on *nix platforms
[bamtools.git] / src / api / internal / io / TcpSocket_p.cpp
1 // ***************************************************************************
2 // TcpSocket_p.cpp (c) 2011 Derek Barnett
3 // Marth Lab, Department of Biology, Boston College
4 // ---------------------------------------------------------------------------
5 // Last modified: 7 December 2011 (DB)
6 // ---------------------------------------------------------------------------
7 // Provides basic TCP I/O interface
8 // ***************************************************************************
9
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;
15
16 #include <algorithm>
17 #include <sstream>
18 #include <vector>
19 using namespace std;
20
21 // ------------------------------------
22 // static utility methods & constants
23 // ------------------------------------
24
25 namespace BamTools {
26 namespace Internal {
27
28 // constants
29 static const size_t DEFAULT_BUFFER_SIZE = 0x4000;
30
31 } // namespace Internal
32 } // namespace BamTools
33
34 // --------------------------
35 // TcpSocket implementation
36 // --------------------------
37
38 TcpSocket::TcpSocket(void)
39     : m_mode(IBamIODevice::NotOpen)
40 //    , m_localPort(0)
41     , m_remotePort(0)
42     , m_engine(0)
43     , m_cachedSocketDescriptor(-1)
44     , m_readBuffer(DEFAULT_BUFFER_SIZE)
45     , m_error(TcpSocket::UnknownSocketError)
46     , m_state(TcpSocket::UnconnectedState)
47 { }
48
49 TcpSocket::~TcpSocket(void) {
50     if ( m_state == TcpSocket::ConnectedState )
51         DisconnectFromHost();
52 }
53
54 size_t TcpSocket::BufferBytesAvailable(void) const {
55     return m_readBuffer.Size();
56 }
57
58 bool TcpSocket::CanReadLine(void) const {
59     return m_readBuffer.CanReadLine();
60 }
61
62 void TcpSocket::ClearBuffer(void) {
63     m_readBuffer.Clear();
64 }
65
66 bool TcpSocket::ConnectImpl(const HostInfo& hostInfo,
67                             const std::string& port,
68                             IBamIODevice::OpenMode mode)
69 {
70     // skip if we're already connected
71     if ( m_state == TcpSocket::ConnectedState ) {
72         m_error = TcpSocket::SocketResourceError; 
73         m_errorString = "socket already connected";
74         return false;
75     }
76
77     // reset socket state
78     m_hostName   = hostInfo.HostName();
79     m_mode       = mode;    
80     m_state      = TcpSocket::UnconnectedState;
81     m_error      = TcpSocket::UnknownSocketError;
82 //    m_localPort  = 0;
83     m_remotePort = 0;
84 //    m_localAddress.Clear();
85     m_remoteAddress.Clear();
86     m_readBuffer.Clear();
87
88     // fetch candidate addresses for requested host
89     vector<HostAddress> addresses = hostInfo.Addresses();
90     if ( addresses.empty() ) {
91         m_error = TcpSocket::HostNotFoundError;
92         m_errorString = "no IP addresses found for host";
93         return false;
94     }
95
96     // convert port string to integer
97     stringstream ss(port);
98     uint16_t portNumber(0);
99     ss >> portNumber;
100
101     // iterate through adddresses
102     vector<HostAddress>::const_iterator addrIter = addresses.begin();
103     vector<HostAddress>::const_iterator addrEnd  = addresses.end();
104     for ( ; addrIter != addrEnd; ++addrIter) {
105         const HostAddress& addr = (*addrIter);
106
107         // try to initialize socket engine with this address
108         if ( !InitializeSocketEngine(addr.GetProtocol()) ) {
109             // failure to initialize is OK here
110             // we'll just try the next available address
111             continue;
112         }
113
114         // attempt actual connection
115         if ( m_engine->Connect(addr, portNumber) ) {
116
117             // if connection successful, update our state & return true
118             m_mode = mode;
119 //            m_localAddress  = m_engine->GetLocalAddress();
120 //            m_localPort     = m_engine->GetLocalPort();
121             m_remoteAddress = m_engine->GetRemoteAddress();
122             m_remotePort    = m_engine->GetRemotePort();
123             m_cachedSocketDescriptor = m_engine->GetSocketDescriptor();
124             m_state = TcpSocket::ConnectedState;
125             return true;
126         }
127     }
128
129     // if we get here, no connection could be made
130     m_error = TcpSocket::HostNotFoundError;
131     m_errorString = "could not connect to any host addresses";
132     return false;
133 }
134
135 bool TcpSocket::ConnectToHost(const string& hostName,
136                               uint16_t port,
137                               IBamIODevice::OpenMode mode)
138 {
139     stringstream ss("");
140     ss << port;
141     return ConnectToHost(hostName, ss.str(), mode);
142
143 }
144
145 bool TcpSocket::ConnectToHost(const string& hostName,
146                               const string& port,
147                               IBamIODevice::OpenMode mode)
148 {
149     // create new address object with requested host name
150     HostAddress hostAddress;
151     hostAddress.SetAddress(hostName);
152
153     HostInfo info;
154     // if host name was IP address ("x.x.x.x" or IPv6 format)
155     // otherwise host name was 'plain-text' ("www.foo.bar")
156     // we need to look up IP address(es)
157     if ( hostAddress.HasIPAddress() ) 
158         info.SetAddresses( vector<HostAddress>(1, hostAddress) );
159     else
160         info = HostInfo::Lookup(hostName, port);
161
162     // attempt connection on requested port
163     return ConnectImpl(info, port, mode);
164 }
165
166 void TcpSocket::DisconnectFromHost(void) {
167
168     // close socket engine & delete
169     if ( m_state == TcpSocket::ConnectedState )
170         ResetSocketEngine();
171
172     // reset connection state
173 //    m_localPort = 0;
174     m_remotePort = 0;
175 //    m_localAddress.Clear();
176     m_remoteAddress.Clear();
177     m_hostName.clear();
178     m_cachedSocketDescriptor = -1;
179
180     // for future, make sure there's outgoing data that needs to be flushed
181     m_readBuffer.Clear();
182 }
183
184 TcpSocket::SocketError TcpSocket::GetError(void) const {
185     return m_error;
186 }
187
188 std::string TcpSocket::GetErrorString(void) const {
189     return m_errorString;
190 }
191
192 std::string TcpSocket::GetHostName(void) const {
193     return m_hostName;
194 }
195
196 //HostAddress TcpSocket::GetLocalAddress(void) const {
197 //    return m_localAddress;
198 //}
199
200 //uint16_t TcpSocket::GetLocalPort(void) const {
201 //    return m_localPort;
202 //}
203
204 HostAddress TcpSocket::GetRemoteAddress(void) const {
205     return m_remoteAddress;
206 }
207
208 uint16_t TcpSocket::GetRemotePort(void) const {
209     return m_remotePort;
210 }
211
212 TcpSocket::SocketState TcpSocket::GetState(void) const {
213     return m_state;
214 }
215
216 bool TcpSocket::InitializeSocketEngine(HostAddress::NetworkProtocol protocol) {
217     ResetSocketEngine();
218     m_engine = new TcpSocketEngine;
219     return m_engine->Initialize(protocol);
220 }
221
222 bool TcpSocket::IsConnected(void) const {
223     if ( m_engine == 0 )
224         return false;
225     return ( m_engine->IsValid() && (m_state == TcpSocket::ConnectedState) );
226 }
227
228 // may be read in a look until desired data amount has been read
229 // returns: number of bytes read, or -1 if error
230 int64_t TcpSocket::Read(char* data, const unsigned int numBytes) {
231
232     // if we have data in buffer, just return it
233     if ( !m_readBuffer.IsEmpty() ) {
234         const size_t bytesRead = m_readBuffer.Read(data, numBytes);
235         return static_cast<int64_t>(bytesRead);
236     }
237
238     // otherwise, we'll need to fetch data from socket
239     // first make sure we have a valid socket engine
240     if ( m_engine == 0 ) {
241         // TODO: set error string/state?
242         return -1;
243     }
244
245     // fetch data from socket, return 0 for success, -1 for failure
246     // since this should be called in a loop,
247     // we'll pull the actual bytes from the buffer on next iteration
248     const int64_t socketBytesRead = ReadFromSocket();
249     if ( socketBytesRead < 0 ) {
250         // TODO: set error string/state ?
251         return -1;
252     }
253
254     // we should have data now in buffer, try to fetch requested amount
255     // if nothing in buffer, we will return 0 bytes read (signals EOF reached)
256     const size_t numBytesRead = m_readBuffer.Read(data, numBytes);
257     return static_cast<int64_t>(numBytesRead);
258 }
259
260 int64_t TcpSocket::ReadFromSocket(void) {
261
262     // check for any socket engine errors
263     if ( !m_engine->IsValid() ) {
264         m_errorString = "TcpSocket::ReadFromSocket - socket disconnected";
265         ResetSocketEngine();
266         return -1;
267     }
268
269     // wait for ready read
270     bool timedOut;
271     const bool isReadyRead = m_engine->WaitForRead(5000, &timedOut);
272
273     // if not ready
274     if ( !isReadyRead ) {
275
276         // if we simply timed out
277         if ( timedOut ) {
278             m_errorString = "TcpSocket::ReadFromSocket - timed out waiting for ready read";
279             // get error from engine ?
280             return -1;
281         }
282
283         // otherwise, there was an error
284         else {
285             m_errorString = "TcpSocket::ReadFromSocket - encountered error while waiting for ready read";
286             // get error from engine ?
287             return -1;
288         }
289     }
290
291     // get number of bytes available from socket
292     const int64_t bytesToRead = m_engine->NumBytesAvailable();
293     if ( bytesToRead < 0 ) {
294         m_errorString = "TcpSocket::ReadFromSocket - encountered error while determining numBytesAvailable";
295         // get error from engine ?
296         return -1;
297     }
298
299     // make space in buffer & read from socket
300     char* buffer = m_readBuffer.Reserve(bytesToRead);
301     const int64_t numBytesRead = m_engine->Read(buffer, bytesToRead);
302     if ( numBytesRead == -1 ) {
303         m_errorString = "TcpSocket::ReadFromSocket - encountered error while reading bytes";
304         // get error from engine ?
305     }
306
307     // return number of bytes actually read
308     return numBytesRead;
309 }
310
311 string TcpSocket::ReadLine(int64_t max) {
312
313     // prep result byte buffer
314     ByteArray result;
315
316     size_t bufferMax = ((max > static_cast<int64_t>(string::npos)) ? string::npos : static_cast<size_t>(max));
317     result.Resize(bufferMax);
318
319     // read data
320     int64_t readBytes(0);
321     if ( result.Size() == 0 ) {
322
323         if ( bufferMax == 0 )
324             bufferMax = string::npos;
325
326         result.Resize(1);
327
328         int64_t readResult;
329         do {
330             result.Resize( static_cast<size_t>(std::min(bufferMax, result.Size() + DEFAULT_BUFFER_SIZE)) );
331             readResult = ReadLine(result.Data()+readBytes, result.Size()-readBytes);
332             if ( readResult > 0 || readBytes == 0 )
333                 readBytes += readResult;
334         } while ( readResult == DEFAULT_BUFFER_SIZE && result[static_cast<size_t>(readBytes-1)] != '\n' );
335
336     } else
337         readBytes = ReadLine(result.Data(), result.Size());
338
339     // clean up byte buffer
340     if ( readBytes <= 0 )
341         result.Clear();
342     else
343         result.Resize(static_cast<size_t>(readBytes));
344
345     // return byte buffer as string
346     return string( result.ConstData(), result.Size() );
347 }
348
349 int64_t TcpSocket::ReadLine(char* dest, size_t max) {
350     
351     // wait for buffer to contain line contents
352     if ( !WaitForReadLine() ) {
353         m_errorString = "TcpSocket::ReadLine - error waiting for read line";
354         return -1;
355     }
356     
357     // leave room for null term
358     if ( max < 2 )
359         return -1;
360     --max;
361
362     // read from buffer, handle newlines
363     int64_t readSoFar = m_readBuffer.ReadLine(dest, max);
364     if ( readSoFar && dest[readSoFar-1] == '\n' ) {
365
366         // adjust for windows-style '\r\n'
367         if ( readSoFar > 1 && dest[readSoFar-2] == '\r') {
368             --readSoFar;
369             dest[readSoFar-1] = '\n';
370         }
371     }
372
373     // null terminate & return number of bytes read
374     dest[readSoFar] = '\0';
375     return readSoFar;
376 }
377
378 void TcpSocket::ResetSocketEngine(void) {
379
380     // shut down socket engine
381     if ( m_engine ) {
382         m_engine->Close();
383         delete m_engine;
384         m_engine = 0;
385     }
386
387     // reset our state & cached socket handle
388     m_state = TcpSocket::UnconnectedState;
389     m_cachedSocketDescriptor = -1;
390 }
391
392 bool TcpSocket::WaitForReadLine(void) {
393
394     // wait until we can read a line (will return immediately if already capable)
395     while ( !CanReadLine() ) {
396         if ( !ReadFromSocket() ) 
397             return false;
398     }
399
400     // if we get here, success  
401     return true;
402 }
403
404 int64_t TcpSocket::Write(const char* data, const unsigned int numBytes) {
405
406     // single-shot attempt at write (not buffered, just try to shove the data through socket)
407     // this method purely exists to send 'small' HTTP requests/FTP commands from client to server
408
409     int64_t bytesWritten(0);
410
411     // wait for our socket to be write-able
412     bool timedOut;
413     const bool isReadyWrite = m_engine->WaitForWrite(3000, &timedOut);
414     if ( isReadyWrite )
415         bytesWritten = m_engine->Write(data, numBytes);
416     else {
417         // timeout is OK (with current setup), we'll just return 0 & try again
418         // but we need to report if engine encountered some other error
419         if ( !timedOut ) {
420             // TODO: set error string
421             bytesWritten = -1;
422         }
423     }
424
425     // return actual number of bytes written to socket
426     return bytesWritten;
427 }