/***************************************************************************
* GVOClient.cpp - VO client class *
* ----------------------------------------------------------------------- *
* copyright (C) 2013-2015 by Juergen Knoedlseder *
* ----------------------------------------------------------------------- *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see . *
* *
***************************************************************************/
/**
* @file GVOClient.cpp
* @brief VO client class implementation
* @author Juergen Knoedlseder
*/
/* __ Includes ___________________________________________________________ */
#ifdef HAVE_CONFIG_H
#include
#endif
#include // std::getenv() function
#include // std::fopen(), etc. functions
#include // std::memset() function
#include // close() function
#include // getaddrinfo() function
#include // socket(), connect() functions
#include "GVOClient.hpp"
#include "GVOHub.hpp"
#include "GException.hpp"
#include "GTools.hpp"
/* __ Method name definitions ____________________________________________ */
#define G_FIND_HUB "GVOClient::find_hub()"
#define G_REQUIRE_HUB "GVOClient::require_hub()"
#define G_REGISTER_TO_HUB "GVOClient::register_to_hub()"
/* __ Macros _____________________________________________________________ */
/* __ Coding definitions _________________________________________________ */
/* __ Debug definitions __________________________________________________ */
//#define G_SHOW_MESSAGE //!< Show posted and received messages
/*==========================================================================
= =
= Constructors/destructors =
= =
==========================================================================*/
/***********************************************************************//**
* @brief Void constructor
***************************************************************************/
GVOClient::GVOClient(void)
{
// Initialise members
init_members();
// Find Hub
find_hub();
// Return
return;
}
/***********************************************************************//**
* @brief Copy constructor
*
* @param[in] client VO client.
***************************************************************************/
GVOClient::GVOClient(const GVOClient& client)
{
// Initialise members
init_members();
// Copy members
copy_members(client);
// Return
return;
}
/***********************************************************************//**
* @brief Destructor
***************************************************************************/
GVOClient::~GVOClient(void)
{
// Free members
free_members();
// Return
return;
}
/*==========================================================================
= =
= Operators =
= =
==========================================================================*/
/***********************************************************************//**
* @brief Assignment operator
*
* @param[in] client VO client.
* @return VO client.
***************************************************************************/
GVOClient& GVOClient::operator=(const GVOClient& client)
{
// Execute only if object is not identical
if (this != &client) {
// Free members
free_members();
// Initialise members
init_members();
// Copy members
copy_members(client);
} // endif: object was not identical
// Return
return *this;
}
/*==========================================================================
= =
= Public methods =
= =
==========================================================================*/
/***********************************************************************//**
* @brief Clear object.
*
* Reset object to a clean initial state.
***************************************************************************/
void GVOClient::clear(void)
{
// Free memory
free_members();
// Initialise members
init_members();
// Return
return;
}
/***********************************************************************//**
* @brief Clone object
***************************************************************************/
GVOClient* GVOClient::clone(void) const
{
// Clone client
return new GVOClient(*this);
}
/***********************************************************************//**
* @brief Register client at VO Hub
*
* Registers a client at the VO Hub. The method will search for a VO Hub,
* launch it's own VO Hub if no Hub was found, and the register the client
* at the Hub, including sending of metadata.
***************************************************************************/
void GVOClient::connect(void)
{
// Require a Hub
require_hub();
// Continue only if we have a Hub
if (has_hub()) {
// Register to Hub
register_to_hub();
// Send meta data
send_metadata();
} // endif: we had a Hub
// Return
return;
}
/***********************************************************************//**
* @brief Unregister client from VO Hub
*
* Disconnects the VO client from the VO Hub. The client is unregistered.
***************************************************************************/
void GVOClient::disconnect(void)
{
// Continue only if we have a Hub
if (has_hub()) {
// Unregister from Hub
unregister_from_hub();
// Close socket
if (m_socket != -1) {
close(m_socket);
m_socket = -1;
}
} // endif: we had a Hub
// Return
return;
}
/***********************************************************************//**
* @brief Signals if client has VO Hub information
*
* @return True if client has VO Hub information, false otherwise.
*
* Checks if all mandatory VO Hub tokens are non-empty.
***************************************************************************/
bool GVOClient::has_hub(void) const
{
// Return Hub information status
return (!m_secret.empty() && !m_hub_url.empty() && !m_version.empty());
}
/***********************************************************************//**
* @brief Signals if client is connected to VO Hub
*
* @return True if client is connected to VO Hub, false otherwise.
*
* Checks if the clients has non-empty client key, Hub ID and client ID.
***************************************************************************/
bool GVOClient::is_connected(void) const
{
// Return Hub information status
return (!m_client_key.empty() && !m_hub_id.empty() && !m_client_id.empty());
}
/***********************************************************************//**
* @brief Ping VO Hub
*
* @return True if VO Hub is alive, false otherwise.
***************************************************************************/
bool GVOClient::ping_hub(void) const
{
// Initialise ping result
bool result = false;
// Declare request
std::string request = "";
// Set request
request.append("\n");
request.append("\n");
request.append(" samp.hub.ping\n");
request.append(" \n");
request.append(" \n");
request.append(" \n");
request.append("\n");
// Execute request
GXml xml = execute(request);
// If we got a valid response then set ping result flag to true
if (response_is_valid(xml)) {
result = true;
}
// Return ping result
return result;
}
/***********************************************************************//**
* @brief Shutdown VO Hub
***************************************************************************/
void GVOClient::shutdown_hub(void) const
{
// Declare request
std::string request = "";
// Set request
request.append("\n");
request.append("\n");
request.append(" samp.hub.shutdown\n");
request.append(" \n");
request.append(" \n");
request.append(" \n");
request.append("\n");
// Execute request
execute(request);
// Return
return;
}
/***********************************************************************//**
* @brief Execute function on server
*
* @param[in] request XML request string
* @return XML response
***************************************************************************/
GXml GVOClient::execute(const std::string& request) const
{
// Initialise response
GXml xml;
// Connect to hub
connect_to_hub();
// Continue only if connection has been established
if (m_socket != -1) {
// Post request
post_string(request);
// Receive response
std::string response = receive_string();
// Find start of XML text
size_t start = response.find("\n");
request.append("\n");
request.append(" samp.hub.register\n");
request.append(" \n");
request.append(" "+m_secret+"\n");
request.append(" \n");
request.append("\n");
// Execute request
GXml xml = execute(request);
// If no error occured, extract hub and client identifiers
if (response_is_valid(xml)) {
m_client_key = get_response_value(xml, "samp.private-key");
m_hub_id = get_response_value(xml, "samp.hub-id");
m_client_id = get_response_value(xml, "samp.self-id");
}
// ... otherwise signal an error
else {
std::string msg = "Unable to connect to Hub (" +
response_error_message(xml) + ", code=" +
gammalib::str(response_error_code(xml)) + ").";
throw GException::runtime_error(G_REGISTER_TO_HUB, msg);
}
// Return
return;
}
/***********************************************************************//**
* @brief Unregister client from SAMP Hub
***************************************************************************/
void GVOClient::unregister_from_hub(void)
{
// Unregister only if we were registered
if (is_connected()) {
// Declare request
std::string request;
// Set metadata header
request.append("\n");
request.append("\n");
request.append(" samp.hub.unregister\n");
request.append(" \n");
request.append(" "+m_client_key+"\n");
request.append(" \n");
request.append("\n");
// Execute request
execute(request);
} // endif: we were registered
// Reset Hub and client identifiers
m_client_key.clear();
m_hub_id.clear();
m_client_id.clear();
// Return
return;
}
/***********************************************************************//**
* @brief Send client metadata to SAMP Hub
***************************************************************************/
void GVOClient::send_metadata(void) const
{
// Declare request
std::string request;
// Set metadata header
request.append("\n");
request.append("\n");
request.append("samp.hub.declareMetadata\n");
request.append("\n");
request.append(" "+m_client_key+"\n");
request.append(" \n");
// Set SAMP name
request.append(" \n");
request.append(" samp.name\n");
request.append(" "+m_name+"\n");
request.append(" \n");
// Set SAMP description text
request.append(" \n");
request.append(" samp.description.text\n");
request.append(" GammaLib client\n");
request.append(" \n");
// Set SAMP icon URL
request.append(" \n");
request.append(" samp.icon.url\n");
request.append(" http://a.fsdn.com/allura/p/gammalib/icon\n");
request.append(" \n");
// Set author affiliation
request.append(" \n");
request.append(" author.affiliation\n");
request.append(" IRAP, Toulouse, France\n");
request.append(" \n");
// Set author e-mail
request.append(" \n");
request.append(" author.email\n");
request.append(" jurgen.knodlseder@irap.omp.eu\n");
request.append(" \n");
// Set author name
request.append(" \n");
request.append(" author.name\n");
request.append(" Juergen Knoedlseder\n");
request.append(" \n");
// Set metadata trailer
request.append(" \n");
request.append("\n");
request.append("\n");
// Execute request
execute(request);
// Return
return;
}
/*==========================================================================
= =
= Low-level private methods =
= =
==========================================================================*/
/***********************************************************************//**
* @brief Connect to SAMP Hub
*
* Connects to Hub by creating a socket and connecting to this socket. The
* method expects that a Hub has already been found. If no Hub has been found
* (i.e. has_hub() returns false), the method does nothing.
*
* The method extracts the hostname and the port from the Hub endpoint URL.
* If Hub connection fails, any created socket is closed.
*
* If the method is successful, m_socket will contain on exit a non-negative
* number. If any failure occurs, m_socket will be set to -1.
***************************************************************************/
void GVOClient::connect_to_hub(void) const
{
// Close any existing socket
if (m_socket != -1) {
close(m_socket);
m_socket = -1;
}
// Continue only if host and port information was found
if (!m_hub_host.empty() && !m_hub_port.empty()) {
// Set hints
struct addrinfo hints;
std::memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
// Get server information
struct addrinfo* servinfo;
if (getaddrinfo(m_hub_host.c_str(), m_hub_port.c_str(),
&hints, &servinfo) == 0) {
// Loop through all the results and connect to the first
// we can
for (struct addrinfo* ptr = servinfo; ptr != NULL; ptr = ptr->ai_next) {
// Create socket
m_socket = socket(ptr->ai_family,
ptr->ai_socktype,
ptr->ai_protocol);
// Connect to socket if socket is valid
if (m_socket != -1) {
if (::connect(m_socket,
ptr->ai_addr,
ptr->ai_addrlen) == -1) {
close(m_socket);
m_socket = -1;
}
else {
// Socket connection successful, break now
break;
}
}
} // endfor: looped through all results
} // endif: server information was valid
} // endif: host and port information found
// Return
return;
}
/***********************************************************************//**
* @brief Post string content to Hub
*
* @param[in] content String content to post
*
* Posts the content of a string to the Hub.
*
* The method does nothing if no Hub connection has been established.
***************************************************************************/
void GVOClient::post_string(const std::string& content) const
{
// Continue only if Hub connection has been established
if (m_socket != -1) {
// Determine content length
int length = content.length();
// Set prefix
std::string prefix = "POST /"+m_hub_path+" HTTP/1.0\n"
"User-Agent: GammaLib\n"
"Content-Type: text/xml\n"
"Content-Length: "+gammalib::str(length)+"\n\n";
// Build post string
std::string post = prefix + content;
// Debug option: show posted message
#if defined(G_SHOW_MESSAGE)
std::cout << post << std::endl;
#endif
// Send content to socket
bool done = false;
do {
int length = post.length();
int sent_length = send(m_socket, post.c_str(), length, 0);
if (sent_length < length) {
post = post.substr(sent_length, std::string::npos);
}
else {
done = true;
}
} while (!done);
} // endif: Hub connection had been established
// Return
return;
}
/***********************************************************************//**
* @brief Receive string content from Hub
*
* @return String received from Hub.
*
* Reads information sent by Hub into a string.
*
* The method does nothing if no Hub connection has been established.
***************************************************************************/
std::string GVOClient::receive_string(void) const
{
// Initialise empty string
std::string result = "";
// Continue only if Hub connection has been established
if (m_socket != -1) {
// Define buffer
char buffer[1001];
// Read buffer until it is empty
int timeout = 2000; // Initial timeout is 2 sec
int n = 0;
do {
n = gammalib::recv(m_socket, buffer, 1000, 0, timeout);
if (n > 0) {
buffer[n] = '\0';
result.append(std::string(buffer));
}
timeout = 10; // The timeout now is 0.01 sec
} while (n > 0);
// Debug option: show received message
#if defined(G_SHOW_MESSAGE)
std::cout << result << std::endl;
#endif
} // endif: Hub connection had been established
// Return result
return result;
}
/***********************************************************************//**
* @brief Returns value for a SAMP Hub response parameter
*
* @param[in] xml Hub response XML document.
* @param[in] name Parameter name.
* @return Parameter value.
*
* Returns value for a SAMP Hub response parameter. If the specified
* parameter was not found or if the response structure is not compliant,
* an empty string is returned.
***************************************************************************/
std::string GVOClient::get_response_value(const GXml& xml,
const std::string& name) const
{
// Declare value
std::string value = "";
// Search for value of specified member
const GXmlNode* node = xml.element("methodResponse > params > param > value > struct");
if (node != NULL) {
int num = node->elements("member");
for (int i = 0; i < num; ++i) {
const GXmlNode* member = node->element("member", i);
std::string one_name;
std::string one_value;
get_name_value_pair(member, one_name, one_value);
if (one_name == name) {
value = one_value;
break;
}
}
}
// Return value
return value;
}
/***********************************************************************//**
* @brief Extract name / value pair from XML node
*
* @param[in] node Pointer to XML node.
* @param[out] name Name string.
* @param[out] value Value string.
*
* Extracts a name / value pair from a XML node. If the XML node pointer is
* NULL, the name and value strings will be empty.
***************************************************************************/
void GVOClient::get_name_value_pair(const GXmlNode* node,
std::string& name,
std::string& value) const
{
// Clear name and value strings
name.clear();
value.clear();
// Continue only if node is valid
if (node != NULL) {
// Get name node and extract text content
const GXmlNode* ptr = node->element("name", 0);
if (ptr != NULL) {
const GXmlText* text = static_cast((*ptr)[0]);
if (text != NULL) {
name = text->text();
}
}
// Get value node and extract text content
ptr = node->element("value", 0);
if (ptr != NULL) {
const GXmlText* text = static_cast((*ptr)[0]);
if (text != NULL) {
value = text->text();
}
}
}
// Return
return;
}
/***********************************************************************//**
* @brief Returns SAMP Hub lockfile URL
*
* @return SAMP Hub lockfile URL (empty if no lockfile was found).
*
* Implements IVOA standard REC-SAMP-1.3-20120411.
***************************************************************************/
std::string GVOClient::get_hub_lockfile(void) const
{
// Initialise result
std::string url = "";
// Check for existence of the SAMP_HUB environment variable first
char* hub_ptr = std::getenv("SAMP_HUB");
if (hub_ptr != NULL) {
// Check for mandatory std-lockurl: prefix (no other prefix is
// supported so far)
std::string lockurl = std::string(hub_ptr);
if (lockurl.compare(0, 12, "std-lockurl:") == 0) {
// Extract URL
url = lockurl.substr(12, std::string::npos);
} // endif: std-lockurl: prefix found
} // endif: SAMP_HUB environment variable found
// ... otherwise the lockfile should be $HOME/.samp
else {
// Get user's HOME directory path as the prefix of the full
// path. If the HOME environment variable is not set we
// expect that .samp is in the local directory. This is non
// standard, but prevents for creating an exception here.
std::string prefix = "";
char* home_ptr = std::getenv("HOME");
if (home_ptr != NULL) {
prefix = std::string(home_ptr) + "/";
}
// Set filename
url = prefix + ".samp";
} // endelse: no SAMP_HUB environment variable found
// Return URL
return url;
}
/***********************************************************************//**
* @brief Checks if response is valid
*
* @return True if response is valid, false otherwise.
***************************************************************************/
bool GVOClient::response_is_valid(const GXml& xml) const
{
// Initialise validity flag
bool valid = true;
// Check for data
const GXmlNode* node = xml.element("methodResponse > params > param > value");
if (node == NULL) {
valid = false;
}
// Return validity
return valid;
}
/***********************************************************************//**
* @brief Return response error code
*
* @return Response error core (0 if no error occured).
***************************************************************************/
int GVOClient::response_error_code(const GXml& xml) const
{
// Initialise error code
int error_code = 0;
// Extract error code
const GXmlNode* node = xml.element("methodResponse > fault > value > struct");
if (node != NULL) {
const GXmlNode* code = node->element("member[0] > value > int");
if (code != NULL) {
error_code = gammalib::toint(static_cast((*code)[0])->text());
}
}
// Return error code
return error_code;
}
/***********************************************************************//**
* @brief Return response error message
*
* @return Response error message (blank if no error occured).
***************************************************************************/
std::string GVOClient::response_error_message(const GXml& xml) const
{
// Initialise error message
std::string error_message;
// Extract error message
const GXmlNode* node = xml.element("methodResponse > fault > value > struct");
if (node != NULL) {
const GXmlNode* msg = node->element("member[1] > value");
if (msg != NULL) {
error_message = static_cast((*msg)[0])->text();
}
}
// Return error message
return error_message;
}