]> git.donarmstrong.com Git - bamtools.git/blob - src/api/internal/io/TcpSocket_p.cpp
Fixed: premature EOF issues & updated Windows implementation
[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: 8 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             // TODO: get add'l error info from engine ?
279             m_errorString = "TcpSocket::ReadFromSocket - timed out waiting for ready read";
280         }
281
282         // otherwise, there was some other error
283         else {
284             // TODO: get add'l error info from engine ?
285             m_errorString = "TcpSocket::ReadFromSocket - encountered error while waiting for ready read";
286         }
287
288         // return failure
289         return -1;
290     }
291
292     // get number of bytes available from socket
293     const int64_t bytesToRead = m_engine->NumBytesAvailable();
294     if ( bytesToRead < 0 ) {
295         // TODO: get add'l error info from engine ?
296         m_errorString = "TcpSocket::ReadFromSocket - encountered error while determining numBytesAvailable";
297         return -1;
298     }
299
300     // make space in buffer & read from socket
301     char* buffer = m_readBuffer.Reserve(bytesToRead);
302     const int64_t numBytesRead = m_engine->Read(buffer, bytesToRead);
303     if ( numBytesRead == -1 ) {
304         // TODO: get add'l error info from engine ?
305         m_errorString = "TcpSocket::ReadFromSocket - encountered error while reading bytes";
306     }
307
308     // return number of bytes actually read
309     return numBytesRead;
310 }
311
312 string TcpSocket::ReadLine(int64_t max) {
313
314     // prep result byte buffer
315     ByteArray result;
316
317     size_t bufferMax = ((max > static_cast<int64_t>(string::npos)) ? string::npos : static_cast<size_t>(max));
318     result.Resize(bufferMax);
319
320     // read data
321     int64_t readBytes(0);
322     if ( result.Size() == 0 ) {
323
324         if ( bufferMax == 0 )
325             bufferMax = string::npos;
326
327         result.Resize(1);
328
329         int64_t readResult;
330         do {
331             result.Resize( static_cast<size_t>(min(bufferMax, result.Size() + DEFAULT_BUFFER_SIZE)) );
332             readResult = ReadLine(result.Data()+readBytes, result.Size()-readBytes);
333             if ( readResult > 0 || readBytes == 0 )
334                 readBytes += readResult;
335         } while ( readResult == DEFAULT_BUFFER_SIZE && result[static_cast<size_t>(readBytes-1)] != '\n' );
336
337     } else
338         readBytes = ReadLine(result.Data(), result.Size());
339
340     // clean up byte buffer
341     if ( readBytes <= 0 )
342         result.Clear();
343     else
344         result.Resize(static_cast<size_t>(readBytes));
345
346     // return byte buffer as string
347     return string( result.ConstData(), result.Size() );
348 }
349
350 int64_t TcpSocket::ReadLine(char* dest, size_t max) {
351
352     // wait for buffer to contain line contents
353     if ( !WaitForReadLine() ) {
354         m_errorString = "TcpSocket::ReadLine - error waiting for read line";
355         return -1;
356     }
357
358     // leave room for null term
359     if ( max < 2 )
360         return -1;
361     --max;
362
363     // read from buffer, handle newlines
364     int64_t readSoFar = m_readBuffer.ReadLine(dest, max);
365     if ( readSoFar && dest[readSoFar-1] == '\n' ) {
366
367         // adjust for windows-style '\r\n'
368         if ( readSoFar > 1 && dest[readSoFar-2] == '\r') {
369             --readSoFar;
370             dest[readSoFar-1] = '\n';
371         }
372     }
373
374     // null terminate & return number of bytes read
375     dest[readSoFar] = '\0';
376     return readSoFar;
377 }
378
379 void TcpSocket::ResetSocketEngine(void) {
380
381     // shut down socket engine
382     if ( m_engine ) {
383         m_engine->Close();
384         delete m_engine;
385         m_engine = 0;
386     }
387
388     // reset our state & cached socket handle
389     m_state = TcpSocket::UnconnectedState;
390     m_cachedSocketDescriptor = -1;
391 }
392
393 bool TcpSocket::WaitForReadLine(void) {
394
395     // wait until we can read a line (will return immediately if already capable)
396     while ( !CanReadLine() ) {
397         if ( !ReadFromSocket() )
398             return false;
399     }
400
401     // if we get here, success
402     return true;
403 }
404
405 int64_t TcpSocket::Write(const char* data, const unsigned int numBytes) {
406
407     // single-shot attempt at write (not buffered, just try to shove the data through socket)
408     // this method purely exists to send 'small' HTTP requests/FTP commands from client to server
409
410     // wait for our socket to be write-able
411     bool timedOut;
412     const bool isReadyWrite = m_engine->WaitForWrite(3000, &timedOut);
413
414     // if ready, return number of bytes written
415     if ( isReadyWrite )
416         return m_engine->Write(data, numBytes);
417
418     // otherwise, socket not ready for writing
419     // set error string depending on reason & return failure
420     if ( !timedOut ) {
421         // TODO: get add'l error info from engine ??
422         m_errorString = "TcpSocket::Write - timed out waiting for ready-write";
423     }
424     else {
425         // TODO: get add'l error info from engine ??
426         m_errorString = "TcpSocket::Write - error encountered while waiting for ready-write";
427     }
428     return -1;
429 }