Tiny HTTP client layer on top of NetChat - and CTest support for some SimGear tests.

This commit is contained in:
James Turner 2011-07-19 12:55:55 +01:00
parent 116c487384
commit f06f25532c
16 changed files with 953 additions and 10 deletions

View File

@ -142,6 +142,11 @@ configure_file (
"${PROJECT_BINARY_DIR}/simgear/simgear_config.h"
)
# enable CTest / make test target
include (Dart)
enable_testing()
install (FILES ${PROJECT_BINARY_DIR}/simgear/simgear_config.h DESTINATION include/simgear/)
add_subdirectory(simgear)
@ -156,3 +161,4 @@ ADD_CUSTOM_TARGET(uninstall
"${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake")

View File

@ -14,6 +14,8 @@ set(HEADERS
sg_serial.hxx
sg_socket.hxx
sg_socket_udp.hxx
HTTPClient.hxx
HTTPRequest.hxx
)
set(SOURCES
@ -28,6 +30,15 @@ set(SOURCES
sg_serial.cxx
sg_socket.cxx
sg_socket_udp.cxx
HTTPClient.cxx
HTTPRequest.cxx
)
simgear_component(io io "${SOURCES}" "${HEADERS}")
simgear_component(io io "${SOURCES}" "${HEADERS}")
add_executable(test_sock socktest.cxx)
target_link_libraries(test_sock sgio sgstructure sgdebug)
add_executable(test_http test_HTTP.cxx)
target_link_libraries(test_http sgio sgstructure sgtiming sgmisc sgdebug)

240
simgear/io/HTTPClient.cxx Normal file
View File

@ -0,0 +1,240 @@
#include "HTTPClient.hxx"
#include <sstream>
#include <cassert>
#include <list>
#include <boost/foreach.hpp>
#include <boost/algorithm/string/case_conv.hpp>
#include <simgear/io/sg_netChat.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/compiler.h>
#include <simgear/debug/logstream.hxx>
#include <simgear/version.h>
using std::string;
using std::stringstream;
using std::vector;
#include <iostream>
using std::cout;
using std::cerr;
using std::endl;
namespace simgear
{
namespace HTTP
{
class Connection : public NetChat
{
public:
Connection(Client* pr) :
client(pr),
state(STATE_IDLE)
{
setTerminator("\r\n");
}
void connectToHost(const string& host)
{
open();
int colonPos = host.find(':');
if (colonPos > 0) {
string h = host.substr(0, colonPos);
int port = strutils::to_int(host.substr(colonPos + 1));
connect(h.c_str(), port);
} else {
connect(host.c_str(), 80 /* default port */);
}
}
void queueRequest(const Request_ptr& r)
{
if (!activeRequest) {
startRequest(r);
} else {
queuedRequests.push_back(r);
}
}
void startRequest(const Request_ptr& r)
{
activeRequest = r;
state = STATE_IDLE;
bodyTransferSize = 0;
stringstream headerData;
string path = r->path();
if (!client->proxyHost().empty()) {
path = "http://" + r->host() + path;
}
int requestTime;
headerData << r->method() << " " << path << " HTTP/1.1 " << client->userAgent() << "\r\n";
headerData << "Host: " << r->host() << "\r\n";
headerData << "X-Time: " << requestTime << "\r\n";
if (!client->proxyAuth().empty()) {
headerData << "Proxy-Authorization: " << client->proxyAuth() << "\r\n";
}
BOOST_FOREACH(string h, r->requestHeaders()) {
headerData << h << ": " << r->header(h) << "\r\n";
}
headerData << "\r\n"; // final CRLF to terminate the headers
// TODO - add request body support for PUT, etc operations
push(headerData.str().c_str());
cout << "sent request" << endl;
}
virtual void collectIncomingData(const char* s, int n)
{
if (state == STATE_GETTING_BODY) {
activeRequest->gotBodyData(s, n);
} else {
buffer += string(s, n);
}
}
virtual void foundTerminator(void)
{
switch (state) {
case STATE_IDLE:
activeRequest->responseStart(buffer);
state = STATE_GETTING_HEADERS;
buffer.clear();
break;
case STATE_GETTING_HEADERS:
processHeader();
buffer.clear();
break;
case STATE_GETTING_BODY:
responseComplete();
state = STATE_IDLE;
setTerminator("\r\n");
if (!queuedRequests.empty()) {
Request_ptr next = queuedRequests.front();
queuedRequests.pop_front();
startRequest(next);
}
break;
}
}
private:
void processHeader()
{
string h = strutils::simplify(buffer);
if (h.empty()) { // blank line terminates headers
headersComplete();
if (bodyTransferSize > 0) {
state = STATE_GETTING_BODY;
cout << "getting body:" << bodyTransferSize << endl;
setByteCount(bodyTransferSize);
} else {
responseComplete();
state = STATE_IDLE; // no response body, we're done
}
return;
}
int colonPos = buffer.find(':');
if (colonPos < 0) {
SG_LOG(SG_IO, SG_WARN, "malformed HTTP response header:" << h);
return;
}
string key = strutils::simplify(buffer.substr(0, colonPos));
string lkey = boost::to_lower_copy(key);
string value = strutils::strip(buffer.substr(colonPos + 1));
if (lkey == "content-length" && (bodyTransferSize <= 0)) {
bodyTransferSize = strutils::to_int(value);
} else if (lkey == "transfer-length") {
bodyTransferSize = strutils::to_int(value);
}
activeRequest->responseHeader(lkey, value);
}
void headersComplete()
{
activeRequest->responseHeadersComplete();
}
void responseComplete()
{
activeRequest->responseComplete();
client->requestFinished(this);
activeRequest = NULL;
}
enum ConnectionState {
STATE_IDLE = 0,
STATE_GETTING_HEADERS,
STATE_GETTING_BODY
};
Client* client;
Request_ptr activeRequest;
ConnectionState state;
std::string buffer;
int bodyTransferSize;
std::list<Request_ptr> queuedRequests;
};
Client::Client()
{
setUserAgent("SimGear-" SG_STRINGIZE(SIMGEAR_VERSION));
}
void Client::makeRequest(const Request_ptr& r)
{
string host = r->host();
if (!_proxy.empty()) {
host = _proxy;
}
if (_connections.find(host) == _connections.end()) {
Connection* con = new Connection(this);
con->connectToHost(host);
_connections[host] = con;
}
_connections[host]->queueRequest(r);
}
void Client::requestFinished(Connection* con)
{
}
void Client::setUserAgent(const string& ua)
{
_userAgent = ua;
}
void Client::setProxy(const string& proxy, const string& auth)
{
_proxy = proxy;
_proxyAuth = auth;
}
} // of namespace HTTP
} // of namespace simgear

51
simgear/io/HTTPClient.hxx Normal file
View File

@ -0,0 +1,51 @@
#ifndef SG_HTTP_CLIENT_HXX
#define SG_HTTP_CLIENT_HXX
#include <map>
#include <simgear/io/HTTPRequest.hxx>
namespace simgear
{
namespace HTTP
{
class Connection;
class Client
{
public:
Client();
void makeRequest(const Request_ptr& r);
void setUserAgent(const std::string& ua);
void setProxy(const std::string& proxy, const std::string& auth = "");
const std::string& userAgent() const
{ return _userAgent; }
const std::string& proxyHost() const
{ return _proxy; }
const std::string& proxyAuth() const
{ return _proxyAuth; }
private:
void requestFinished(Connection* con);
friend class Connection;
std::string _userAgent;
std::string _proxy;
std::string _proxyAuth;
// connections by host
std::map<std::string, Connection*> _connections;
};
} // of namespace HTTP
} // of namespace simgear
#endif // of SG_HTTP_CLIENT_HXX

133
simgear/io/HTTPRequest.cxx Normal file
View File

@ -0,0 +1,133 @@
#include "HTTPRequest.hxx"
#include <simgear/misc/strutils.hxx>
#include <simgear/compiler.h>
#include <simgear/debug/logstream.hxx>
using std::string;
using std::map;
#include <iostream>
using std::cout;
using std::cerr;
using std::endl;
namespace simgear
{
namespace HTTP
{
Request::Request(const string& url, const string method) :
_method(method),
_url(url)
{
}
Request::~Request()
{
}
string_list Request::requestHeaders() const
{
string_list r;
return r;
}
string Request::header(const std::string& name) const
{
return string();
}
void Request::responseStart(const string& r)
{
const int maxSplit = 2; // HTTP/1.1 nnn status code
string_list parts = strutils::split(r, NULL, maxSplit);
_responseStatus = strutils::to_int(parts[1]);
_responseReason = parts[2];
}
void Request::responseHeader(const string& key, const string& value)
{
_responseHeaders[key] = value;
}
void Request::responseHeadersComplete()
{
// no op
}
void Request::gotBodyData(const char* s, int n)
{
}
void Request::responseComplete()
{
}
string Request::scheme() const
{
int firstColon = url().find(":");
if (firstColon > 0) {
return url().substr(0, firstColon);
}
return ""; // couldn't parse scheme
}
string Request::path() const
{
string u(url());
int schemeEnd = u.find("://");
if (schemeEnd < 0) {
return ""; // couldn't parse scheme
}
int hostEnd = u.find('/', schemeEnd + 3);
if (hostEnd < 0) {
return ""; // couldn't parse host
}
int query = u.find('?', hostEnd + 1);
if (query < 0) {
// all remainder of URL is path
return u.substr(hostEnd);
}
return u.substr(hostEnd, query - hostEnd);
}
string Request::host() const
{
string u(url());
int schemeEnd = u.find("://");
if (schemeEnd < 0) {
return ""; // couldn't parse scheme
}
int hostEnd = u.find('/', schemeEnd + 3);
if (hostEnd < 0) { // all remainder of URL is host
return u.substr(schemeEnd + 3);
}
return u.substr(schemeEnd + 3, hostEnd - (schemeEnd + 3));
}
int Request::contentLength() const
{
HeaderDict::const_iterator it = _responseHeaders.find("content-length");
if (it == _responseHeaders.end()) {
return 0;
}
return strutils::to_int(it->second);
}
} // of namespace HTTP
} // of namespace simgear

View File

@ -0,0 +1,69 @@
#ifndef SG_HTTP_REQUEST_HXX
#define SG_HTTP_REQUEST_HXX
#include <map>
#include <simgear/structure/SGReferenced.hxx>
#include <simgear/structure/SGSharedPtr.hxx>
#include <simgear/math/sg_types.hxx>
namespace simgear
{
namespace HTTP
{
class Request : public SGReferenced
{
public:
virtual ~Request();
virtual std::string method() const
{ return _method; }
virtual std::string url() const
{ return _url; }
virtual std::string scheme() const;
virtual std::string path() const;
virtual std::string host() const; // host, including port
virtual string_list requestHeaders() const;
virtual std::string header(const std::string& name) const;
virtual int responseCode() const
{ return _responseStatus; }
virtual std::string resposeReason() const
{ return _responseReason; }
virtual int contentLength() const;
protected:
friend class Connection;
Request(const std::string& url, const std::string method = "get");
virtual void responseStart(const std::string& r);
virtual void responseHeader(const std::string& key, const std::string& value);
virtual void responseHeadersComplete();
virtual void responseComplete();
virtual void gotBodyData(const char* s, int n);
private:
std::string _method;
std::string _url;
int _responseStatus;
std::string _responseReason;
typedef std::map<std::string, std::string> HeaderDict;
HeaderDict _responseHeaders;
};
typedef SGSharedPtr<Request> Request_ptr;
} // of namespace HTTP
} // of namespace simgear
#endif // of SG_HTTP_REQUEST_HXX

View File

@ -26,14 +26,15 @@
#include <simgear/io/sg_netChat.hxx>
#include <cstring> // for strdup
namespace simgear {
void
NetChat::setTerminator (const char* t)
{
if (terminator) delete[] terminator;
if (terminator) free(terminator);
terminator = strdup(t);
bytesToCollect = -1;
}
const char*
@ -42,6 +43,15 @@ NetChat::getTerminator (void)
return terminator;
}
void
NetChat::setByteCount(int count)
{
if (terminator) free(terminator);
terminator = NULL;
bytesToCollect = count;
}
// return the size of the largest prefix of needle at the end
// of haystack
@ -89,12 +99,22 @@ NetChat::handleBufferRead (NetBuffer& in_buffer)
// necessary because we might read several data+terminator combos
// with a single recv().
while (in_buffer.getLength()) {
while (in_buffer.getLength()) {
// special case where we're not using a terminator
if (terminator == 0 || *terminator == 0) {
collectIncomingData (in_buffer.getData(),in_buffer.getLength());
in_buffer.remove ();
if (terminator == 0 || *terminator == 0) {
if ( bytesToCollect > 0) {
const int toRead = std::min(in_buffer.getLength(), bytesToCollect);
collectIncomingData(in_buffer.getData(), toRead);
in_buffer.remove(0, toRead);
bytesToCollect -= toRead;
if (bytesToCollect == 0) { // read all requested bytes
foundTerminator();
}
} else { // read the whole lot
collectIncomingData (in_buffer.getData(),in_buffer.getLength());
in_buffer.remove ();
}
return;
}

View File

@ -61,6 +61,7 @@
#ifndef SG_NET_CHAT_H
#define SG_NET_CHAT_H
#include <memory>
#include <simgear/io/sg_netBuffer.hxx>
namespace simgear
@ -69,16 +70,25 @@ namespace simgear
class NetChat : public NetBufferChannel
{
char* terminator;
int bytesToCollect;
virtual void handleBufferRead (NetBuffer& buffer) ;
public:
NetChat () : terminator (0) {}
NetChat () :
terminator (NULL),
bytesToCollect(-1)
{}
void setTerminator (const char* t);
const char* getTerminator (void);
/**
* set byte count to collect - 'foundTerminator' will be called once
* this many bytes have been collected
*/
void setByteCount(int bytes);
bool push (const char* s);
virtual void collectIncomingData (const char* s, int n) {}

274
simgear/io/test_HTTP.cxx Normal file
View File

@ -0,0 +1,274 @@
#include <cstdlib>
#include <iostream>
#include <map>
#include <sstream>
#include <boost/algorithm/string/case_conv.hpp>
#include "HTTPClient.hxx"
#include "HTTPRequest.hxx"
#include <simgear/io/sg_netChat.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/timing/timestamp.hxx>
using std::cout;
using std::cerr;
using std::endl;
using std::string;
using std::stringstream;
using namespace simgear;
const char* BODY1 = "The quick brown fox jumps over a lazy dog.";
const int body2Size = 8 * 1024;
char body2[body2Size];
#define COMPARE(a, b) \
if ((a) != (b)) { \
cerr << "failed:" << #a << " != " << #b << endl; \
cerr << "\tgot:" << a << endl; \
exit(1); \
}
#define VERIFY(a) \
if (!(a)) { \
cerr << "failed:" << #a << endl; \
exit(1); \
}
class TestRequest : public HTTP::Request
{
public:
bool complete;
string bodyData;
TestRequest(const std::string& url) :
HTTP::Request(url),
complete(false)
{
}
protected:
virtual void responseHeadersComplete()
{
}
virtual void responseComplete()
{
complete = true;
}
virtual void gotBodyData(const char* s, int n)
{
bodyData += string(s, n);
}
};
class TestServerChannel : public NetChat
{
public:
enum State
{
STATE_IDLE = 0,
STATE_HEADERS,
STATE_REQUEST_BODY
};
TestServerChannel()
{
state = STATE_IDLE;
setTerminator("\r\n");
}
virtual void collectIncomingData(const char* s, int n)
{
buffer += string(s, n);
}
virtual void foundTerminator(void)
{
if (state == STATE_IDLE) {
state = STATE_HEADERS;
string_list line = strutils::split(buffer, NULL, 3);
if (line.size() < 4) {
cerr << "malformed request:" << buffer << endl;
exit(-1);
}
method = line[0];
path = line[1];
httpVersion = line[2];
userAgent = line[3];
requestHeaders.clear();
buffer.clear();
} else if (state == STATE_HEADERS) {
string s = strutils::simplify(buffer);
if (s.empty()) {
buffer.clear();
receivedRequestHeaders();
return;
}
int colonPos = buffer.find(':');
if (colonPos < 0) {
cerr << "malformed HTTP response header:" << buffer << endl;
buffer.clear();
return;
}
string key = strutils::simplify(buffer.substr(0, colonPos));
string lkey = boost::to_lower_copy(key);
string value = strutils::strip(buffer.substr(colonPos + 1));
requestHeaders[lkey] = value;
buffer.clear();
} else if (state == STATE_REQUEST_BODY) {
}
}
void receivedRequestHeaders()
{
state = STATE_IDLE;
if (path == "/test1") {
string contentStr(BODY1);
stringstream d;
d << "HTTP1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
d << "Content-Length:" << contentStr.size() << "\r\n";
d << "\r\n"; // final CRLF to terminate the headers
d << contentStr;
push(d.str().c_str());
} else if (path == "/test2") {
stringstream d;
d << "HTTP1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
d << "Content-Length:" << body2Size << "\r\n";
d << "\r\n"; // final CRLF to terminate the headers
push(d.str().c_str());
bufferSend(body2, body2Size);
cout << "sent body2" << endl;
} else {
sendErrorResponse(404);
}
}
void sendErrorResponse(int code)
{
cerr << "sending error " << code << " for " << path << endl;
stringstream headerData;
headerData << "HTTP1.1 " << code << " " << reasonForCode(code) << "\r\n";
headerData << "\r\n"; // final CRLF to terminate the headers
push(headerData.str().c_str());
}
string reasonForCode(int code)
{
switch (code) {
case 200: return "OK";
case 404: return "not found";
default: return "unknown code";
}
}
State state;
string buffer;
string method;
string path;
string httpVersion;
string userAgent;
std::map<string, string> requestHeaders;
};
class TestServer : public NetChannel
{
public:
TestServer()
{
open();
bind(NULL, 2000); // localhost, any port
listen(5);
}
virtual ~TestServer()
{
}
virtual bool writable (void) { return false ; }
virtual void handleAccept (void)
{
simgear::IPAddress addr ;
int handle = accept ( &addr ) ;
TestServerChannel* chan = new TestServerChannel();
chan->setHandle(handle);
}
};
void waitForComplete(TestRequest* tr)
{
SGTimeStamp start(SGTimeStamp::now());
while (start.elapsedMSec() < 1000) {
NetChannel::poll(10);
if (tr->complete) {
return;
}
}
cerr << "timed out" << endl;
}
int main(int argc, char* argv[])
{
TestServer s;
HTTP::Client cl;
// test URL parsing
TestRequest* tr1 = new TestRequest("http://localhost:2000/test1?foo=bar");
COMPARE(tr1->scheme(), "http");
COMPARE(tr1->host(), "localhost:2000");
COMPARE(tr1->path(), "/test1");
// basic get request
{
TestRequest* tr = new TestRequest("http://localhost:2000/test1");
HTTP::Request_ptr own(tr);
cl.makeRequest(tr);
waitForComplete(tr);
COMPARE(tr->responseCode(), 200);
COMPARE(tr->contentLength(), strlen(BODY1));
COMPARE(tr->bodyData, string(BODY1));
}
// larger get request
for (int i=0; i<body2Size; ++i) {
body2[i] = (i << 4) | (i >> 2);
}
{
TestRequest* tr = new TestRequest("http://localhost:2000/test2");
HTTP::Request_ptr own(tr);
cl.makeRequest(tr);
waitForComplete(tr);
COMPARE(tr->responseCode(), 200);
COMPARE(tr->contentLength(), body2Size);
COMPARE(tr->bodyData, string(body2, body2Size));
}
// test 404
{
TestRequest* tr = new TestRequest("http://localhost:2000/not-found");
HTTP::Request_ptr own(tr);
cl.makeRequest(tr);
waitForComplete(tr);
COMPARE(tr->responseCode(), 404);
COMPARE(tr->contentLength(), 0);
}
cout << "all tests passed ok" << endl;
return EXIT_SUCCESS;
}

View File

@ -33,3 +33,11 @@ set(SOURCES
)
simgear_component(misc misc "${SOURCES}" "${HEADERS}")
add_executable(test_tabbed_values tabbed_values_test.cxx)
add_test(tabbed_values ${EXECUTABLE_OUTPUT_PATH}/test_tabbed_values)
target_link_libraries(test_tabbed_values sgmisc)
add_executable(test_strings strutils_test.cxx )
add_test(test_strings ${EXECUTABLE_OUTPUT_PATH}/test_strings)
target_link_libraries(test_strings sgmisc)

View File

@ -213,5 +213,41 @@ namespace simgear {
return (n != string::npos) && (n == s.length() - substr.length());
}
string simplify(const string& s)
{
string result; // reserve size of 's'?
string::const_iterator it = s.begin(),
end = s.end();
// advance to first non-space char - simplifes logic in main loop,
// since we can always prepend a single space when we see a
// space -> non-space transition
for (; (it != end) && isspace(*it); ++it) { /* nothing */ }
bool lastWasSpace = false;
for (; it != end; ++it) {
char c = *it;
if (isspace(c)) {
lastWasSpace = true;
continue;
}
if (lastWasSpace) {
result.push_back(' ');
}
lastWasSpace = false;
result.push_back(c);
}
return result;
}
int to_int(const std::string& s)
{
return atoi(s.c_str());
}
} // end namespace strutils
} // end namespace simgear

View File

@ -116,6 +116,14 @@ namespace simgear {
*/
bool ends_with( const std::string & s, const std::string & substr );
/**
* Strip all leading/trailing whitespace, and transform all interal
* whitespace into a single ' ' character - i.e newlines/carriage returns/
* tabs/multiple spaces will be condensed.
*/
std::string simplify(const std::string& s);
int to_int(const std::string& s);
} // end namespace strutils
} // end namespace simgear

View File

@ -0,0 +1,58 @@
////////////////////////////////////////////////////////////////////////
// Test harness.
////////////////////////////////////////////////////////////////////////
#include <simgear/compiler.h>
#include <iostream>
#include "strutils.hxx"
using std::cout;
using std::cerr;
using std::endl;
using namespace simgear::strutils;
#define COMPARE(a, b) \
if ((a) != (b)) { \
cerr << "failed:" << #a << " != " << #b << endl; \
exit(1); \
}
#define VERIFY(a) \
if (!(a)) { \
cerr << "failed:" << #a << endl; \
exit(1); \
}
int main (int ac, char ** av)
{
std::string a("abcd");
COMPARE(strip(a), a);
COMPARE(strip(" a "), "a");
COMPARE(lstrip(" a "), "a ");
COMPARE(rstrip("\ta "), "\ta");
// check internal spacing is preserved
COMPARE(strip("\t \na \t b\r \n "), "a \t b");
VERIFY(starts_with("banana", "ban"));
VERIFY(!starts_with("abanana", "ban"));
VERIFY(starts_with("banana", "banana")); // pass - string starts with itself
VERIFY(!starts_with("ban", "banana")); // fail - original string is prefix of
VERIFY(ends_with("banana", "ana"));
VERIFY(ends_with("foo.text", ".text"));
VERIFY(!ends_with("foo.text", ".html"));
COMPARE(simplify("\ta\t b \nc\n\r \r\n"), "a b c");
COMPARE(simplify("The quick - brown dog!"), "The quick - brown dog!");
COMPARE(simplify("\r\n \r\n \t \r"), "");
COMPARE(to_int("999"), 999);
COMPARE(to_int("0000000"), 0);
COMPARE(to_int("-10000"), -10000);
cout << "all tests passed successfully!" << endl;
return 0;
}

View File

@ -20,3 +20,11 @@ set(SOURCES
)
simgear_component(props props "${SOURCES}" "${HEADERS}")
add_executable(test_props props_test.cxx)
target_link_libraries(test_props sgprops sgxml sgstructure sgmisc sgdebug)
add_test(test_props ${EXECUTABLE_OUTPUT_PATH}/test_props)
add_executable(test_propertyObject propertyObject_test.cxx)
target_link_libraries(test_propertyObject sgprops sgstructure sgdebug)
add_test(test_propertyObject ${EXECUTABLE_OUTPUT_PATH}/test_propertyObject)

View File

@ -104,3 +104,10 @@ void SGTimeStamp::stamp() {
#endif
}
int SGTimeStamp::elapsedMSec() const
{
SGTimeStamp now;
now.stamp();
return static_cast<int>((now - *this).toMSecs());
}

View File

@ -195,6 +195,10 @@ public:
static SGTimeStamp now()
{ SGTimeStamp ts; ts.stamp(); return ts; }
/**
* elapsed time since the stamp was taken, in msec
*/
int elapsedMSec() const;
private:
SGTimeStamp(sec_type sec, nsec_type nsec)
{ setTime(sec, nsec); }