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