]> git.donarmstrong.com Git - bamtools.git/blob - src/api/internal/io/TcpSocket_p.cpp
eb9e760532262a45aa0a0aae3e5f8c19e4db675b
[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/TcpSocket_p.h"
11 #include "api/internal/io/TcpSocketEngine_p.h"
12 using namespace BamTools;
13 using namespace BamTools::Internal;
14
15 #include <sstream>
16 #include <vector>
17 using namespace std;
18
19 // ------------------------------------
20 // static utility methods & constants
21 // ------------------------------------
22
23 namespace BamTools {
24 namespace Internal {
25
26 // constants
27 static const size_t DEFAULT_BUFFER_SIZE = 0x8000;
28
29 } // namespace Internal
30 } // namespace BamTools
31
32 // --------------------------
33 // TcpSocket implementation
34 // --------------------------
35
36 TcpSocket::TcpSocket(void)
37     : m_mode(IBamIODevice::NotOpen)
38 //    , m_localPort(0)
39     , m_remotePort(0)
40     , m_engine(0)
41     , m_cachedSocketDescriptor(-1)
42     , m_readBuffer(DEFAULT_BUFFER_SIZE)
43     , m_error(TcpSocket::UnknownSocketError)
44     , m_state(TcpSocket::UnconnectedState)
45 { }
46
47 TcpSocket::~TcpSocket(void) {
48     if ( m_state == TcpSocket::ConnectedState )
49         DisconnectFromHost();
50 }
51
52 size_t TcpSocket::BufferBytesAvailable(void) const {
53     return m_readBuffer.Size();
54 }
55
56 bool TcpSocket::CanReadLine(void) const {
57     return m_readBuffer.CanReadLine();
58 }
59
60 void TcpSocket::ClearBuffer(void) {
61     m_readBuffer.Clear();
62 }
63
64 bool TcpSocket::ConnectImpl(const HostInfo& hostInfo,
65                             const std::string& port,
66                             IBamIODevice::OpenMode mode)
67 {
68     // skip if we're already connected
69     if ( m_state == TcpSocket::ConnectedState ) {
70         m_error = TcpSocket::SocketResourceError;
71         return false;
72     }
73
74     // reset socket state
75     m_mode =  mode;
76     m_hostName = hostInfo.HostName();
77     m_state = TcpSocket::UnconnectedState;
78     m_error = TcpSocket::UnknownSocketError;
79 //    m_localPort  = 0;
80     m_remotePort = 0;
81 //    m_localAddress.Clear();
82     m_remoteAddress.Clear();
83     m_readBuffer.Clear();
84
85     // fetch candidate addresses for requested host
86     vector<HostAddress> addresses = hostInfo.Addresses();
87     if ( addresses.empty() ) {
88         m_error = TcpSocket::HostNotFoundError;
89         return false;
90     }
91
92     // convert port string to integer
93     stringstream ss(port);
94     uint16_t portNumber(0);
95     ss >> portNumber;
96
97     // iterate through adddresses
98     vector<HostAddress>::const_iterator addrIter = addresses.begin();
99     vector<HostAddress>::const_iterator addrEnd  = addresses.end();
100     for ( ; addrIter != addrEnd; ++addrIter) {
101         const HostAddress& addr = (*addrIter);
102
103         // try to initialize socket engine with this address
104         if ( !InitializeSocketEngine(addr.GetProtocol()) ) {
105             // failure to initialize is OK here
106             // we'll just try the next available address
107             continue;
108         }
109
110         // attempt actual connection
111         if ( m_engine->Connect(addr, portNumber) ) {
112
113             // if connection successful, update our state & return true
114             m_mode = mode;
115 //            m_localAddress  = m_engine->GetLocalAddress();
116 //            m_localPort     = m_engine->GetLocalPort();
117             m_remoteAddress = m_engine->GetRemoteAddress();
118             m_remotePort    = m_engine->GetRemotePort();
119             m_cachedSocketDescriptor = m_engine->GetSocketDescriptor();
120             m_state = TcpSocket::ConnectedState;
121             return true;
122         }
123     }
124
125     // if we get here, no connection could be made
126     m_error = TcpSocket::HostNotFoundError;
127     return false;
128 }
129
130 bool TcpSocket::ConnectToHost(const string& hostName,
131                               uint16_t port,
132                               IBamIODevice::OpenMode mode)
133 {
134     stringstream ss("");
135     ss << port;
136     return ConnectToHost(hostName, ss.str(), mode);
137
138 }
139
140 bool TcpSocket::ConnectToHost(const string& hostName,
141                               const string& port,
142                               IBamIODevice::OpenMode mode)
143 {
144     // create new address object with requested host name
145     HostAddress hostAddress;
146     hostAddress.SetAddress(hostName);
147
148     HostInfo info;
149     // if host name was IP address ("x.x.x.x" or IPv6 format)
150     // otherwise host name was 'plain-text' ("www.foo.bar")
151     // we need to look up IP address(es)
152     if ( hostAddress.HasIPAddress() )
153         info.SetAddresses( vector<HostAddress>(1, hostAddress) );
154     else
155         info = HostInfo::Lookup(hostName, port);
156
157     // attempt connection on requested port
158     return ConnectImpl(info, port, mode);
159 }
160
161 void TcpSocket::DisconnectFromHost(void) {
162
163     // close socket engine & delete
164     if ( m_state == TcpSocket::ConnectedState )
165         ResetSocketEngine();
166
167     // reset connection state
168 //    m_localPort = 0;
169     m_remotePort = 0;
170 //    m_localAddress.Clear();
171     m_remoteAddress.Clear();
172     m_hostName.clear();
173     m_cachedSocketDescriptor = -1;
174 }
175
176 TcpSocket::SocketError TcpSocket::GetError(void) const {
177     return m_error;
178 }
179
180 std::string TcpSocket::GetErrorString(void) const {
181     return m_errorString;
182 }
183
184 std::string TcpSocket::GetHostName(void) const {
185     return m_hostName;
186 }
187
188 //HostAddress TcpSocket::GetLocalAddress(void) const {
189 //    return m_localAddress;
190 //}
191
192 //uint16_t TcpSocket::GetLocalPort(void) const {
193 //    return m_localPort;
194 //}
195
196 HostAddress TcpSocket::GetRemoteAddress(void) const {
197     return m_remoteAddress;
198 }
199
200 uint16_t TcpSocket::GetRemotePort(void) const {
201     return m_remotePort;
202 }
203
204 TcpSocket::SocketState TcpSocket::GetState(void) const {
205     return m_state;
206 }
207
208 bool TcpSocket::InitializeSocketEngine(HostAddress::NetworkProtocol protocol) {
209     ResetSocketEngine();
210     m_engine = new TcpSocketEngine;
211     return m_engine->Initialize(protocol);
212 }
213
214 bool TcpSocket::IsConnected(void) const {
215     if ( m_engine == 0 )
216         return false;
217     return ( m_engine->IsValid() && (m_state == TcpSocket::ConnectedState) );
218 }
219
220 // may be read in a look until desired data amount has been read
221 // returns: number of bytes read, or -1 if error
222 int64_t TcpSocket::Read(char* data, const unsigned int numBytes) {
223
224     // if we have data in buffer, just return it
225     if ( !m_readBuffer.IsEmpty() ) {
226         const size_t bytesRead = m_readBuffer.Read(data, numBytes);
227         return static_cast<int64_t>(bytesRead);
228     }
229
230     // otherwise, we'll need to fetch data from socket
231     // first make sure we have a valid socket engine
232     if ( m_engine == 0 ) {
233         // TODO: set error string/state?
234         return -1;
235     }
236
237     // fetch data from socket, return 0 for success, -1 for failure
238     // since this should be called in a loop, we'll pull the actual bytes on next iteration
239     return ( ReadFromSocket() ? 0 : -1 );
240 }
241
242 bool TcpSocket::ReadFromSocket(void) {
243
244     // wait for ready read
245     bool timedOut;
246     bool isReadyRead = m_engine->WaitForRead(5000, &timedOut);
247
248     // if not ready
249     if ( !isReadyRead ) {
250
251         // if we simply timed out
252         if ( timedOut ) {
253             // TODO: set error string
254             return false;
255         }
256
257         // otherwise, there was an error
258         else {
259             // TODO: set error string
260             return false;
261         }
262     }
263
264     // #########################################################################
265     // clean this up - smells funky, but it's a key step so it has to be right
266     // #########################################################################
267     // get number of bytes available from socket
268     // (if 0, still try to read some data so we don't trigger any OS event behavior
269     //  that respond to repeated access to a remote closed socket)
270     int64_t bytesToRead = m_engine->NumBytesAvailable();
271     if ( bytesToRead < 0 )
272         return false;
273     else if ( bytesToRead == 0 )
274         bytesToRead = 4096;
275
276     // make space in buffer & read from socket
277     char* buffer = m_readBuffer.Reserve(bytesToRead);
278     int64_t numBytesRead = m_engine->Read(buffer, bytesToRead);
279
280     // (Qt uses -2 for no data, not error)
281     // squeeze buffer back down & return success
282     if ( numBytesRead == -2 ) {
283         m_readBuffer.Chop(bytesToRead);
284         return true;
285     }
286     // #########################################################################
287
288     // check for any socket engine errors
289     if ( !m_engine->IsValid() ) {
290         // TODO: set error string
291         ResetSocketEngine();
292         return false;
293     }
294
295     // return success
296     return true;
297 }
298
299 string TcpSocket::ReadLine(void) {
300     if ( m_readBuffer.CanReadLine() )
301         return m_readBuffer.ReadLine();
302     return string();
303 }
304
305 void TcpSocket::ResetSocketEngine(void) {
306
307     // shut down socket engine
308     if ( m_engine ) {
309         m_engine->Close();
310         delete m_engine;
311         m_engine = 0;
312     }
313
314     // reset our state & cached socket handle
315     m_state = TcpSocket::UnconnectedState;
316     m_cachedSocketDescriptor = -1;
317 }
318
319 int64_t TcpSocket::Write(const char* data, const unsigned int numBytes) {
320
321     // single-shot attempt at write (not buffered, just try to shove the data through socket)
322     // this method purely exists to send 'small' HTTP requests/FTP commands from client to server
323
324     int64_t bytesWritten(0);
325
326     // wait for our socket to be write-able
327     bool timedOut;
328     bool isReadyWrite = m_engine->WaitForWrite(3000, &timedOut);
329     if ( isReadyWrite )
330         bytesWritten = m_engine->Write(data, numBytes);
331     else {
332         // timeout is OK (with current setup), we'll just return 0 & try again
333         // but we need to report if engine encountered some other error
334         if ( !timedOut ) {
335             // TODO: set error string
336             bytesWritten = -1;
337         }
338     }
339
340     // return actual number of bytes written to socket
341     return bytesWritten;
342 }