Adds immediate migration results

This commit is contained in:
torsten committed 2026-08-18 22:12:24 +02:00
1 parent ba4121728b
commit 6a2a0667ab
2395 files changed
+357322 -2524

No files matched your search

@@ -0,0 +1,144 @@
/*
* $Id$
*
* (c) Copyright, Real-Time Innovations, 2016-.
* All rights reserved.
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef HPP_SERVICE_DYNAMIC_BUFFER_HPP_
#define HPP_SERVICE_DYNAMIC_BUFFER_HPP_
// IMPORTANT: macros.hpp must be the first RTI header included in every header
// file so that symbols are exported correctly on Windows
#include <dds/core/macros.hpp>
#include "osapi/osapi_heap.h"
#include <dds/core/Value.hpp>
#include <dds/core/types.hpp>
#include <rti/apputils/ServiceException.hpp>
namespace rti { namespace apputils {
class DynamicBufferProperty {
public:
DynamicBufferProperty(int32_t min_size, bool trim_to_size)
:min_size_(min_size), trim_to_size_(trim_to_size)
{
}
DynamicBufferProperty(const DynamicBufferProperty &property)
:min_size_(property.min_size_), trim_to_size_(property.trim_to_size_)
{
}
DynamicBufferProperty& min_size(int32_t min_size)
{
min_size_ = min_size;
return *this;
}
int32_t min_size() const
{
return min_size_;
}
DynamicBufferProperty& trim_to_size(bool trim_to_size)
{
trim_to_size_ = trim_to_size;
return *this;
}
bool trim_to_size() const
{
return trim_to_size_;
}
private:
int32_t min_size_;
bool trim_to_size_;
};
class DynamicBuffer {
public:
DynamicBuffer(const DynamicBufferProperty &property)
:property_(property), buffer_(NULL), buffer_length_(0)
{
ensure_length(property.min_size());
}
~DynamicBuffer()
{
if (buffer_ != NULL) {
RTIOsapiHeap_freeArray(buffer_);
buffer_ = NULL;
}
buffer_length_ = 0;
}
void ensure_length(int32_t new_length)
{
if (new_length > buffer_length_) {
if (!RTIOsapiHeap_reallocateArray(&buffer_, new_length, char)) {
RTI_THROW_SERVICE_EXCEPTION(
&RTI_OSAPI_MEMORY_LOG_OUT_OF_HEAP_STRING_d,
new_length);
}
buffer_length_ = new_length;
}
}
void trim_to_size()
{
if (property_.trim_to_size()) {
if (!RTIOsapiHeap_reallocateArray(
&buffer_,
property_.min_size(),
char)) {
RTI_THROW_SERVICE_EXCEPTION(
&RTI_OSAPI_MEMORY_LOG_OUT_OF_HEAP_STRING_d,
property_.min_size());
}
buffer_length_ = property_.min_size();
}
}
void release(char *&buffer, int32_t& length)
{
buffer = buffer_;
length = buffer_length_;
buffer_ = NULL;
buffer_length_ = 0;
}
void own(char *buffer, int32_t length)
{
buffer_ = buffer;
buffer_length_ = length;
}
char *buffer()
{
return buffer_;
}
int32_t length()
{
return buffer_length_;
}
private:
const DynamicBufferProperty property_;
char* buffer_;
int32_t buffer_length_;
};
} } /* namespace rti::apputils */
#endif /* HPP_SERVICE_DYNAMIC_BUFFER_HPP_ */
@@ -0,0 +1,247 @@
/*
* (c) Copyright, Real-Time Innovations, 2016-.
* All rights reserved.
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef HPP_SERVICE_DYNAMIC_DATA_CONVERTER_HPP_
#define HPP_SERVICE_DYNAMIC_DATA_CONVERTER_HPP_
// IMPORTANT: macros.hpp must be the first RTI header included in every header
// file so that symbols are exported correctly on Windows
#include <dds/core/macros.hpp>
#include "osapi/osapi_heap.h"
#include <dds/core/Value.hpp>
#include <dds/core/types.hpp>
#include <assert.h>
#include <dds/core/xtypes/DynamicType.hpp>
#include <dds/core/xtypes/DynamicData.hpp>
#include <rti/core/NativeValueType.hpp>
#include <rti/apputils/ServiceException.hpp>
#include <rti/apputils/DynamicBuffer.hpp>
#include "apputils/apputils_dynamic_data_converter_impl.h"
namespace rti { namespace apputils {
class DynamicDataConverter {
public:
class ScopedSample {
public:
ScopedSample(
APPUTILSDynamicDataConverter *converter,
DDS_DynamicData *native) :
converter_(converter),
native_(native)
{
}
~ScopedSample()
{
APPUTILSDynamicDataConverter_return_sample(converter_, native_);
}
dds::core::xtypes::DynamicData& get()
{
return rti::core::native_conversions::cast_from_native<
dds::core::xtypes::DynamicData>(*native_);
}
private:
APPUTILSDynamicDataConverter *converter_;
DDS_DynamicData *native_;
};
class ScopedBuffer {
public:
ScopedBuffer() : converter_(NULL), native_(NULL), native_length_(0)
{
}
ScopedBuffer(
APPUTILSDynamicDataConverter *converter,
void *native,
uint32_t native_length) :
converter_(converter),
native_(native),
native_length_(native_length)
{
}
~ScopedBuffer()
{
if (native_ != NULL) {
APPUTILSDynamicDataConverter_return_buffer(converter_, native_);
}
}
void clear()
{
assign(converter_, NULL, 0);
}
void assign(
APPUTILSDynamicDataConverter *converter,
void *native,
uint32_t native_length)
{
if (native_ != NULL) {
APPUTILSDynamicDataConverter_return_buffer(
converter_,
const_cast<void *>(native_));
}
converter_ = converter;
native_ = native;
native_length_ = native_length;
}
ScopedBuffer& operator =(ScopedBuffer& other)
{
assign(other.converter_, other.native_, other.native_length_);
return *this;
}
const void * get()
{
return native_;
}
template <typename T>
const T * get() {
return static_cast<const T *>(native_);
}
uint32_t length()
{
return native_length_;
}
private:
APPUTILSDynamicDataConverter *converter_;
void *native_;
uint32_t native_length_;
};
/**
* The dynamic data converter allows for deferred initialization. When doing
* so, it can be initialized at any moment by calling the set() method.
*/
DynamicDataConverter() : initialized_(false) {}
/**
* Direct initialization constructor. A valid type-code has to be provided
* (not null and it has to be a non-primitive and non null type-kind type).
*/
DynamicDataConverter(
const dds::core::xtypes::DynamicType& type_code,
const DynamicBufferProperty &property) :
initialized_(false)
{
set(type_code, property);
}
/**
* Initialize this dynamic data converter. A valid type-code has to be
* provided (not null and it has to be a non-primitive and non null
* type-kind type).
* If the converter has already been initialized, this method is a no-op.
*/
void set(
const dds::core::xtypes::DynamicType& type_code,
const DynamicBufferProperty &property)
{
if (!initialized_) {
if (!APPUTILSDynamicDataConverter_initialize(
&converter_,
&(type_code.native()),
(RTI_UINT32) property.min_size())) {
RTI_THROW_SERVICE_EXCEPTION(
&RTI_LOG_INIT_FAILURE_s,
"native converter");
}
initialized_ = true;
}
}
~DynamicDataConverter()
{
if (initialized_) {
APPUTILSDynamicDataConverter_finalize(&converter_);
}
}
ScopedSample sample(
const dds::core::xtypes::DynamicData& sample,
const DDS_DataRepresentationId_t target_representation)
{
DDS_DynamicData *native = APPUTILSDynamicDataConverter_get_sample(
&converter_,
const_cast<DDS_DynamicData *>(&sample.native()),
target_representation);
if (native == NULL) {
RTI_THROW_SERVICE_EXCEPTION(
&RTI_LOG_ANY_FAILURE_s,
"get native sample");
}
return ScopedSample(&converter_, native);
}
ScopedBuffer buffer(
const dds::core::xtypes::DynamicData& sample,
const DDS_DataRepresentationId_t target_representation)
{
DDS_UnsignedLong native_length = 0;
void *native = APPUTILSDynamicDataConverter_get_buffer(
&converter_,
&native_length,
const_cast<DDS_DynamicData *>(&sample.native()),
target_representation);
if (native == NULL) {
RTI_THROW_SERVICE_EXCEPTION(
&RTI_LOG_ANY_FAILURE_s,
"get native buffer");
}
return ScopedBuffer(&converter_, native, (uint32_t) native_length);
}
void buffer(
ScopedBuffer& scoped_buffer,
const dds::core::xtypes::DynamicData& sample,
const DDS_DataRepresentationId_t target_representation)
{
DDS_UnsignedLong native_length = 0;
/*
* The scoped buffer has to be cleared before we can use it again. The
* buffer will automatically be returned to this converter. After this,
* we can safely get the buffer again from the dynamic data sample and
* reassign to the scoped buffer.
*/
scoped_buffer.clear();
void *native = APPUTILSDynamicDataConverter_get_buffer(
&converter_,
&native_length,
const_cast<DDS_DynamicData *>(&sample.native()),
target_representation);
if (native == NULL) {
RTI_THROW_SERVICE_EXCEPTION(
&RTI_LOG_ANY_FAILURE_s,
"get native buffer");
}
scoped_buffer.assign(&converter_, native, (uint32_t) native_length);
}
private:
APPUTILSDynamicDataConverter converter_;
bool initialized_;
};
} } /* namespace rti::apputils */
#endif /* HPP_SERVICE_DYNAMIC_DATA_CONVERTER_HPP_ */
@@ -0,0 +1,243 @@
/*
* (c) Copyright, Real-Time Innovations, 2017-.
* All rights reserved.
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*
*/
#ifndef HPP_SERVICE_ENTITY_ID_HPP_
#define HPP_SERVICE_ENTITY_ID_HPP_
// IMPORTANT: macros.hpp must be the first RTI header included in every header
// file so that symbols are exported correctly on Windows
#include <dds/core/macros.hpp>
#include "rtixmlutils/rtixmlutils_object.h"
namespace rti { namespace apputils {
class EntityFullNameIterator {
public:
EntityFullNameIterator(const std::string& full_name)
: full_name_(full_name), substring_pos_(0), next_substring_pos_(0)
{
next();
}
~EntityFullNameIterator()
{
}
bool is_end() const
{
return substring_pos_ == full_name_.length();
}
EntityFullNameIterator& end()
{
substring_pos_ = full_name_.length();
next_substring_pos_ = substring_pos_;
return *this;
}
EntityFullNameIterator& last()
{
next_substring_pos_ = full_name_.length();
substring_pos_ = next_substring_pos_;
for (; substring_pos_ > 0; --substring_pos_) {
if (full_name_[substring_pos_] == '\"') {
size_t opening_quotes = full_name_.rfind(
"\"",
substring_pos_ - 1);
if (opening_quotes == std::string::npos) {
DDSLog_warn(
&RTI_LOG_ANY_s,
"detected unbalanced escaped name. Missing closing '\"'?");
} else {
// skip all characters in escaped name
substring_pos_ = opening_quotes;
}
} else if (full_name_.find(
RTIXMLUTILS_OBJECT_NAME_SEPARATOR,
substring_pos_) == substring_pos_) {
substring_pos_ += strlen(RTIXMLUTILS_OBJECT_NAME_SEPARATOR);
break;
}
}
return *this;
}
std::string parent()
{
size_t parent_length = (substring_pos_ == 0)
? 0
: substring_pos_ - strlen(RTIXMLUTILS_OBJECT_NAME_SEPARATOR);
return full_name_.substr(0, parent_length);
}
std::string operator*() const
{
size_t substring_pos = substring_pos_;
size_t name_length = next_substring_pos_ - substring_pos_;
if (name_length > 1
&& full_name_[substring_pos_ + name_length - 1] == '\"'
&& full_name_[substring_pos_] == '\"') {
name_length -= 2;
++substring_pos;
}
return full_name_.substr(substring_pos, name_length);
}
EntityFullNameIterator& operator++()
{
next();
return *this;
}
std::string escaped()
{
size_t substring_pos = substring_pos_;
size_t name_length = next_substring_pos_ - substring_pos_;
return full_name_.substr(substring_pos, name_length);
}
friend bool operator==(
const EntityFullNameIterator &s1,
const EntityFullNameIterator &s2)
{
return s1.substring_pos_ == s2.substring_pos_
&& s1.next_substring_pos_ == s2.next_substring_pos_
&& &s1.full_name_ == &s2.full_name_;
}
friend bool operator!=(
const EntityFullNameIterator &s1,
const EntityFullNameIterator &s2)
{
return !(s1 == s2);
}
private:
void next()
{
if (full_name_.find(
RTIXMLUTILS_OBJECT_NAME_SEPARATOR,
next_substring_pos_) == next_substring_pos_) {
next_substring_pos_ += strlen(RTIXMLUTILS_OBJECT_NAME_SEPARATOR);
}
substring_pos_ = next_substring_pos_;
for (; next_substring_pos_ < full_name_.length(); ++next_substring_pos_) {
if (full_name_[next_substring_pos_] == '\"') {
size_t closing_quotes = full_name_.find(
"\"",
next_substring_pos_ + 1);
if (closing_quotes == std::string::npos) {
DDSLog_warn(
&RTI_LOG_ANY_s,
"detected unbalanced escaped name. Missing closing '\"'?");
} else {
// skip all characters in escaped name
next_substring_pos_ = closing_quotes;
}
} else if (full_name_.find(
RTIXMLUTILS_OBJECT_NAME_SEPARATOR,
next_substring_pos_) == next_substring_pos_) {
break;
}
}
}
private:
const std::string& full_name_;
size_t substring_pos_;
size_t next_substring_pos_;
};
template<typename ENTITY_KIND_TYPE>
class EntityId {
public:
typedef EntityFullNameIterator name_iterator;
public:
EntityId(const std::string& the_full_name, ENTITY_KIND_TYPE the_kind)
: full_name_(the_full_name), kind_(the_kind)
{
}
bool is_nil() const
{
return kind_ == ENTITY_KIND_TYPE::count_;
}
name_iterator begin() const
{
return EntityFullNameIterator(full_name_);
}
name_iterator end() const
{
return EntityFullNameIterator(full_name_).end();
}
const std::string& full_name() const
{
return full_name_;
}
const std::string name() const
{
return (*EntityFullNameIterator(full_name_).last());
}
const std::string parent_name() const
{
return EntityFullNameIterator(full_name_).last().parent();
}
const ENTITY_KIND_TYPE& kind() const
{
return kind_;
}
static EntityId<ENTITY_KIND_TYPE> nil()
{
static EntityId<ENTITY_KIND_TYPE> nil(
"",
ENTITY_KIND_TYPE::count_);
return nil;
}
friend bool operator==(
const EntityId<ENTITY_KIND_TYPE>& left,
const EntityId<ENTITY_KIND_TYPE>& right)
{
return left.kind_ == right.kind_
&& left.full_name_ == right.full_name_;
}
private:
std::string full_name_;
ENTITY_KIND_TYPE kind_;
};
} } /* namespace rti::apputils */
#endif //HPP_SERVICE_ENTITY_ID_HPP_
@@ -0,0 +1,395 @@
/*
* $Id$
*
* (c) Copyright, Real-Time Innovations, 2016-.
* All rights reserved.
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef HPP_SERVICE_RESOURCE_IDENTIFIER_HPP_
#define HPP_SERVICE_RESOURCE_IDENTIFIER_HPP_
// IMPORTANT: macros.hpp must be the first RTI header included in every header
// file so that symbols are exported correctly on Windows
#include <dds/core/macros.hpp>
#include <assert.h>
#include "reda/reda_inlineList.h"
#include "apputils/apputils_resource_identifier_impl.h"
#include <dds/core/Value.hpp>
#include <rti/core/NativeValueType.hpp>
#include <rti/apputils/ServiceException.hpp>
#include <rti/apputils/EntityId.hpp>
namespace rti { namespace apputils {
class ResourceIdentifier;
class RelativeResource {
public:
typedef const APPUTILSRelativeResourceInfo native_type;
RelativeResource(native_type *native_value)
: native_relative_resource_(native_value)
{
}
~RelativeResource()
{
}
RelativeResource(const RelativeResource &other)
: native_relative_resource_(other.native_relative_resource_)
{
}
RelativeResource operator * () const
{
return RelativeResource(*this);
}
RelativeResource & operator = (const RelativeResource& other)
{
native_relative_resource_ = other.native_relative_resource_;
return *this;
}
RelativeResource& operator ++ ()
{
native_relative_resource_ = next();
return *this;
}
RelativeResource& operator -- ()
{
native_relative_resource_ = prev();
return *this;
}
friend bool operator == (
const RelativeResource & s1,
const RelativeResource & s2)
{
return s1.native_relative_resource_ == s2.native_relative_resource_;
}
friend bool operator != (
const RelativeResource & s1,
const RelativeResource & s2)
{
return !(s1 == s2);
}
std::string name() const
{
return APPUTILSRelativeResourceInfo_get_name(
native_relative_resource_);
}
std::string kind() const
{
return APPUTILSRelativeResourceInfo_get_kind(
native_relative_resource_);
}
RelativeResource& last()
{
REDAInlineListNode *last = (struct REDAInlineListNode *)
REDAInlineList_getLast(
native_relative_resource_->_node.inlineList);
assert(last != NULL);
native_relative_resource_ = reinterpret_cast<native_type*>(last);
return *this;
}
RelativeResource& begin()
{
REDAInlineListNode *first = (struct REDAInlineListNode *)
REDAInlineList_getFirst(
native_relative_resource_->_node.inlineList);
assert(first != NULL);
native_relative_resource_ = reinterpret_cast<native_type*> (first);
return *this;
}
friend const std::string to_string(
const RelativeResource& relative_id)
{
return relative_id.kind() + "/" + relative_id.name();
}
friend class ResourceIdentifier;
private:
static RelativeResource nil()
{
static RelativeResource nil(NULL);
return nil;
}
native_type *next()
{
return reinterpret_cast<native_type *>(
REDAInlineListNode_getNext(&native_relative_resource_->_node));
}
native_type * prev()
{
void *prev =
REDAInlineListNode_getPrev(&native_relative_resource_->_node);
if (prev != NULL) {
return reinterpret_cast<native_type*> (prev);
}
return native_relative_resource_;
}
private:
native_type *native_relative_resource_;
};
class ResourceIdentifier {
public:
typedef APPUTILSResourceIdentifier native_type;
typedef RelativeResource iterator;
typedef RelativeResource relative_resource;
public:
ResourceIdentifier()
{
if (!APPUTILSResourceIdentifier_initialize(&native_)) {
RTI_THROW_SERVICE_EXCEPTION(
&RTI_LOG_INIT_FAILURE_s,
"native resource identifier");
}
}
ResourceIdentifier(const std::string& formatted_uri)
{
if (!APPUTILSResourceIdentifier_initialize(&native_)) {
RTI_THROW_SERVICE_EXCEPTION(
&RTI_LOG_INIT_FAILURE_s,
"native resource identifier");
}
if (!APPUTILSResourceIdentifier_parse_formatted_identifier(
&native_,
formatted_uri.c_str())) {
RTI_THROW_SERVICE_EXCEPTION(
&RTI_SERVICE_LOG_MALFORMED_RESOURCE_IDENTIFIER_s,
formatted_uri.c_str());
}
parse(formatted_uri);
}
ResourceIdentifier(const ResourceIdentifier& other)
{
if (!APPUTILSResourceIdentifier_initialize(&native_)) {
RTI_THROW_SERVICE_EXCEPTION(
&RTI_LOG_INIT_FAILURE_s,
"native resource identifier");
}
append(other);
}
ResourceIdentifier(
const ResourceIdentifier& parent,
const ResourceIdentifier& child)
{
if (!APPUTILSResourceIdentifier_initialize(&native_)) {
RTI_THROW_SERVICE_EXCEPTION(
&RTI_LOG_INIT_FAILURE_s,
"native resource identifier");
}
append(parent);
append(child);
}
ResourceIdentifier(
const std::string& kind,
const std::string& name)
{
if (!APPUTILSResourceIdentifier_initialize(&native_)) {
RTI_THROW_SERVICE_EXCEPTION(
&RTI_LOG_INIT_FAILURE_s,
"native resource identifier");
}
add(kind, name);
}
ResourceIdentifier(
const ResourceIdentifier::relative_resource& relative_resource)
{
if (!APPUTILSResourceIdentifier_initialize(&native_)) {
RTI_THROW_SERVICE_EXCEPTION(
&RTI_LOG_INIT_FAILURE_s,
"native resource identifier");
}
add(relative_resource);
}
ResourceIdentifier& parse(const std::string& formatted_uri)
{
if (!APPUTILSResourceIdentifier_parse_formatted_identifier(
&native_,
formatted_uri.c_str())) {
RTI_THROW_SERVICE_EXCEPTION(
&RTI_SERVICE_LOG_MALFORMED_RESOURCE_IDENTIFIER_s,
formatted_uri.c_str());
}
return *this;
}
~ResourceIdentifier()
{
APPUTILSResourceIdentifier_finalize(&native_);
}
iterator last() const
{
return iterator(APPUTILSResourceIdentifier_get_resource(
const_cast<native_type*> (&native_)));
}
iterator end() const
{
return iterator::nil();
}
iterator begin() const
{
iterator last = this->last();
if (last != end()) {
return last.begin();
}
return last;
}
ResourceIdentifier parent() const
{
ResourceIdentifier parent_id;
iterator it = begin();
for (; it != last(); ++it) {
parent_id += ResourceIdentifier(it.kind(), it.name());
}
return parent_id;
}
std::string fully_qualified_name() const
{
const auto result = APPUTILSResourceIdentifier_get_fully_qualified_name(
const_cast<native_type *>(&native_));
return (result == nullptr) ? "" : result;
}
std::string fully_qualified_kind() const
{
const auto result = APPUTILSResourceIdentifier_get_fully_qualified_kind(
const_cast<native_type *>(&native_));
return (result == nullptr) ? "" : result;
}
std::string custom_method() const
{
const auto result = APPUTILSResourceIdentifier_get_custom_method(
const_cast<native_type *>(&native_));
return (result == nullptr) ? "" : result;
}
ResourceIdentifier& operator+=(
const ResourceIdentifier& right)
{
return append(right);
}
friend std::ostream& operator<<(
std::ostream& os,
const ResourceIdentifier& resource_id)
{
os << to_string(resource_id);
return os;
}
friend const std::string to_string(
const ResourceIdentifier& resource_id)
{
const char* resource_id_str = to_native_string(resource_id);
if (resource_id_str != NULL) {
return std::string(resource_id_str);
} else {
return std::string("");
}
}
friend const char* to_native_string(
const ResourceIdentifier& resource_id)
{
return APPUTILSResourceIdentifier_to_string(
const_cast<native_type*>(&resource_id.native_));
}
native_type& native() const
{
return const_cast<native_type&>(native_);
}
private:
ResourceIdentifier& add(
const std::string& kind,
const std::string& name = "")
{
if (!APPUTILSResourceIdentifier_add_relative_resource(
&native_,
kind.c_str(),
name.c_str())) {
RTI_THROW_SERVICE_EXCEPTION(
&RTI_LOG_ADD_FAILURE_s,
"relative resource");
}
return *this;
}
ResourceIdentifier& add(
const ResourceIdentifier::relative_resource& relative_resource)
{
add(relative_resource.kind(), relative_resource.name());
return *this;
}
ResourceIdentifier& append(
const ResourceIdentifier& right)
{
ResourceIdentifier::iterator it = right.begin();
for (; it != end(); ++it) {
add(it);
}
return *this;
}
native_type native_;
};
} } /* namespace rti::apputils */
#endif /* HPP_SERVICE_RESOURCE_IDENTIFIER_HPP_ */
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,205 @@
/*
* $Id$
*
* (c) Copyright, Real-Time Innovations, 2016-.
* All rights reserved.
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef HPP_SERVICE_SERVICE_EXCEPTION_HPP_
#define HPP_SERVICE_SERVICE_EXCEPTION_HPP_
// IMPORTANT: macros.hpp must be the first RTI header included in every header
// file so that symbols are exported correctly on Windows
#include <dds/core/macros.hpp>
#include <dds/core/types.hpp>
#include <rti/core/Exception.hpp>
namespace rti { namespace apputils {
static
void initialize_exception_message(
std::string& message,
const char *throwing_context,
const RTILogMessage *log_message,
va_list ap)
{
char message_buffer[RTI_LOG_MSG_SIZE_MAX];
RTIOsapiUtility_unusedReturnValue(
RTIOsapiUtility_vsnprintf(
message_buffer,
RTI_LOG_MSG_SIZE_MAX,
log_message->format,
ap),
int);
RTIOsapiUtility_unusedReturnValue(
RTIOsapiUtility_snprintf(
&message[0],
RTI_LOG_MSG_SIZE_MAX,
"%s:%s",
throwing_context,
message_buffer),
int);
}
static
void initialize_exception_message(
std::string& message,
const char *throwing_context,
const RTILogMessageTemplate *log_message,
const char *parameterizedString,
va_list ap)
{
char message_buffer[RTI_LOG_MSG_SIZE_MAX];
const char *SEPARATOR_STR = " | ";
RTIOsapiUtility_unusedReturnValue(
RTIOsapiUtility_vsnprintf(
message_buffer,
RTI_LOG_MSG_SIZE_MAX,
parameterizedString,
ap),
int);
RTIOsapiUtility_unusedReturnValue(
RTIOsapiUtility_snprintf(
&message[0],
RTI_LOG_MSG_SIZE_MAX,
"%s%s%s:%s",
throwing_context,
log_message->format,
strlen(log_message->format) > 0 ? SEPARATOR_STR : "",
message_buffer),
int);
}
/**
* @brief Exception class for Services
*/
class ServiceException : public std::exception {
public:
ServiceException(
const char *throwing_context,
const RTILogMessage *log_message,
...)
: message_(RTI_LOG_MSG_SIZE_MAX, '\0'),
message_id_(log_message->messageId)
{
va_list ap;
va_start(ap, log_message);
initialize_exception_message(
message_,
throwing_context,
log_message,
ap);
va_end(ap);
}
ServiceException(
const char *throwing_context,
const RTILogMessageTemplate *log_message,
const char *parameterizedString,
...)
: message_(RTI_LOG_MSG_SIZE_MAX, '\0'),
message_id_(log_message->messageId)
{
va_list ap;
va_start(ap, parameterizedString);
initialize_exception_message(
message_,
throwing_context,
log_message,
parameterizedString,
ap);
va_end(ap);
}
uint32_t message_id() const
{
return message_id_;
}
virtual const char* what() const throw ()
{
return message_.c_str();
}
virtual ~ServiceException() throw ()
{
}
private:
ServiceException() : message_id_(0u)
{
}
std::string message_;
std::uint32_t message_id_;
};
/**
* @brief Fatal exception class for Services. This exception will cause
* the underlying service to call the shutdown hook.
*/
class FatalServiceException : public ServiceException {
public:
template <typename... Args>
FatalServiceException(
const char *throwing_context,
const RTILogMessage *log_message,
Args... args)
: ServiceException(throwing_context, log_message, args...)
{ }
template <typename... Args>
FatalServiceException(
const char *throwing_context,
const RTILogMessageTemplate *log_message,
const char *parameterizedString,
Args... args)
: ServiceException(
throwing_context,
log_message,
parameterizedString,
args...)
{ }
virtual ~FatalServiceException() throw()
{ }
};
} } /* namespace rti::apputils */
#define RTI_THROW_SERVICE_EXCEPTION(...) \
throw rti::apputils::ServiceException(\
RTI_FUNCTION_NAME,\
__VA_ARGS__)
#define RTI_THROW_FATAL_SERVICE_EXCEPTION(...) \
throw rti::apputils::FatalServiceException(\
RTI_FUNCTION_NAME,\
__VA_ARGS__)
#define RTI_SERVICE_CHECK_CONDITION(EXPRESSION) \
if(!(EXPRESSION)) { \
RTI_THROW_SERVICE_EXCEPTION( \
&RTI_LOG_ANY_s, \
__FILE__ ":" RTI_TOSTRING(__LINE__) ": condition failed: " #EXPRESSION); \
}
#define RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(EXPRESSION, EXTRA_INFO) \
if(!(EXPRESSION)) { \
RTI_THROW_SERVICE_EXCEPTION( \
&RTI_LOG_ANY_ss, \
__FILE__ ":" RTI_TOSTRING(__LINE__) ": condition failed: " #EXPRESSION ", ", \
EXTRA_INFO); \
}
#endif /* HPP_SERVICE_SERVICE_EXCEPTION_HPP_ */
@@ -0,0 +1,295 @@
/*
* (c) Copyright, Real-Time Innovations, 2022.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef STRING_BUFFER_HPP
#define STRING_BUFFER_HPP
#include "rti/apputils/ServiceException.hpp"
namespace rti { namespace apputils {
// Default value of a StringBuffer
constexpr size_t STRING_BUFFER_LENGTH = 1000;
/**
* A GenericStringBuffer is a smart buffer that allocates an INITIAL_CAPACITY
* in the stack and allocates more capacity in the heap if needed. The template
* parameter INITIAL_CAPACITY determines the capacity that is allocated in the
* stack. In case there is a resize which involves heap allocation, the
* destructor of the GenericStringBuffer class frees the memory.
*/
template <std::size_t INITIAL_CAPACITY>
class GenericStringBuffer {
public:
/**
* This constructor creates a new empty GenericStringBuffer with
* INITIAL_CAPACITY.
*/
GenericStringBuffer() : capacity_(INITIAL_CAPACITY), c_str_ptr_(nullptr)
{
RTIOsapiMemory_zero(c_str_, capacity_);
c_str_ptr_ = c_str_;
}
/**
* This constructor creates a new GenericStringBuffer ensuring the size
* of the input parameter. It allocates memory in the heap if necessary.
* @param size Size to ensure.
*/
GenericStringBuffer(const size_t size)
: capacity_(INITIAL_CAPACITY), c_str_ptr_(nullptr)
{
RTIOsapiMemory_zero(c_str_, capacity_);
c_str_ptr_ = c_str_;
ensure_capacity(size);
}
/**
* This constructor creates a new GenericStringBuffer object containing
* an input string. It ensures whatever capacity is necessary for the input
* string.
* @param str std::string that initializes the GenericStringBuffer.
*/
GenericStringBuffer(const std::string& str)
: capacity_(INITIAL_CAPACITY), c_str_ptr_(nullptr)
{
RTIOsapiMemory_zero(c_str_, capacity_);
c_str_ptr_ = c_str_;
ensure_capacity(str.length() + 1);
RTIOsapiUtility_unusedReturnValue(
RTIOsapiUtility_strcpy(c_str_ptr_, str.length(), str.c_str()),
char *);
}
/**
* Copy constructor.
* @param other String buffer that we want to copy
*/
GenericStringBuffer(const GenericStringBuffer& other)
: capacity_(INITIAL_CAPACITY), c_str_ptr_(nullptr)
{
RTIOsapiMemory_zero(c_str_, capacity_);
c_str_ptr_ = c_str_;
ensure_capacity(other.length() + 1);
RTIOsapiUtility_unusedReturnValue(
RTIOsapiUtility_strcpy(
c_str_ptr_,
other.length(),
other.c_str()),
char *);
}
/**
* Move constructor.
* @param other String buffer that we want to move
*/
GenericStringBuffer(GenericStringBuffer&& other)
: capacity_(INITIAL_CAPACITY), c_str_ptr_(nullptr)
{
*this = std::move(other);
}
/**
* Destructor of the GenericStringBuffer. It frees whatever memory may
* have been allocated by the ensure_capacity method().
*/
~GenericStringBuffer()
{
if (capacity_ > INITIAL_CAPACITY) {
DDS_String_free(c_str_ptr_);
c_str_ptr_ = nullptr;
}
}
/**
* Copy-assignment operator
* @param other The buffer we want to copy.
* @return Object in which we have copied.
*/
GenericStringBuffer& operator=(const GenericStringBuffer& other)
{
if (this != &other) {
ensure_capacity(other.length() + 1);
RTIOsapiUtility_unusedReturnValue(
RTIOsapiUtility_strcpy(
c_str_ptr_,
other.length(),
other.c_str()),
char *);
}
return *this;
}
/**
* Move-assignment operator
* @param other The buffer we want to move.
* @return Object in which we have moved.
*/
GenericStringBuffer& operator=(GenericStringBuffer&& other)
{
if (this != &other) {
if (other.capacity_ <= INITIAL_CAPACITY) {
RTIOsapiMemory_move(c_str_, other.c_str_, INITIAL_CAPACITY);
c_str_ptr_ = c_str_;
} else {
capacity_ = other.capacity_;
c_str_ptr_ = other.c_str_ptr_;
other.capacity_ = INITIAL_CAPACITY;
other.c_str_ptr_ = nullptr;
}
}
return *this;
}
/**
* Returns the number of bytes that can be written in the internal char *
* buffer
* @return Capacity of the internal char * buffer.
*/
size_t capacity() const
{
return capacity_;
}
/**
* Returns the length of the internal char * buffer. That is the strlen
* of whatever is in there.
* @return Size of the string stored in the internal char * buffer.
*/
size_t length() const
{
return strlen(c_str_ptr_);
}
/**
* Returns a const pointer to the internal char * buffer.
* @return Const pointer to the internal char * buffer.
*/
const char *c_str() const
{
return c_str_ptr_;
}
/**
* Returns a pointer to the internal char * buffer
* @return Pointer to the internal char * buffer.
*/
char *c_str_ptr()
{
return c_str_ptr_;
}
/**
* This method ensures a capacity by either asserting that the
* GenericStringBuffer has enough space to allocate a string, or
* reallocating some space in the heap. If that is the case more space is
* allocated in the heap via DDS_String_alloc().
* @param capacity Size that needs to be ensured.
*/
void ensure_capacity(const size_t capacity)
{
if (capacity > capacity_) {
// We need to free the string if it was already allocated, this
// happens when _capacity > INITIAL_CAPACITY
if (capacity_ > INITIAL_CAPACITY) {
DDS_String_free(c_str_ptr_);
}
// DDS_String_alloc adds one to the input parameter. We could
// spare a byte, but we want to be precise.
c_str_ptr_ = DDS_String_alloc(capacity - 1);
if (c_str_ptr_ == nullptr) {
RTI_THROW_SERVICE_EXCEPTION(
&RTI_LOG_ANY_FAILURE_s,
"Error allocating string.");
}
RTIOsapiMemory_zero(c_str_ptr_, capacity_);
capacity_ = capacity;
}
}
private:
size_t capacity_;
char *c_str_ptr_;
char c_str_[INITIAL_CAPACITY];
};
/**
* A StringBuffer is a GenericStringBuffer with an initial STRING_BUFFER_LENGTH.
*/
typedef GenericStringBuffer<STRING_BUFFER_LENGTH> StringBuffer;
/**
* This method saves a "freeform string" into a StringBuffer object.
* @param [in,out] string_buffer StringBuffer in which the input string will
* be saved. Note that if there's not enough capacity, the method will
* automatically allocate more space for the string.
* @param format Format of the string to be saved.
* @param ... Parameters to the formatted string.
*/
inline void StringBuffer_save_freeform(
StringBuffer& string_buffer,
const char *format,
...)
{
char *c_str_ptr = string_buffer.c_str_ptr();
size_t capacity = string_buffer.capacity();
va_list args, args_aux;
RTIXMLSaveContext save_ctxt = RTIXMLSaveContext_INITIALIZER;
RTIXMLSaveContext_initialize(&save_ctxt);
save_ctxt.sout = c_str_ptr;
if (!RTIOsapiUtility_sizeToUInt32(save_ctxt.ssize, capacity)) {
RTI_THROW_SERVICE_EXCEPTION(
&RTI_LOG_ANY_FAILURE_s,
"Error allocating string.");
}
// Wrap RTIXMLSaveContext_freeform_w_argsI call with va_start and va_end
// of the va_list that we need to pass in.
va_start(args, format);
va_start(args_aux, format);
RTIXMLSaveContext_freeform_v(&save_ctxt, format, args, args_aux);
va_end(args);
va_end(args_aux);
if (save_ctxt.outputStringLength + 1 > capacity) {
capacity = save_ctxt.outputStringLength + 1;
string_buffer.ensure_capacity(capacity);
RTIXMLSaveContext_initialize(&save_ctxt);
save_ctxt.sout = string_buffer.c_str_ptr();
if (!RTIOsapiUtility_sizeToUInt32(save_ctxt.ssize, capacity)) {
RTI_THROW_SERVICE_EXCEPTION(
&RTI_LOG_ANY_FAILURE_s,
"Error allocating string.");
}
// Wrap RTIXMLSaveContext_freeform_w_argsI call with va_start and
// va_end of the va_list that we need to pass in.
va_start(args, format);
va_start(args_aux, format);
RTIXMLSaveContext_freeform_v(&save_ctxt, format, args, args_aux);
va_end(args);
va_end(args_aux);
}
}
}} // namespace rti::apputils
#endif // STRING_BUFFER_HPP
@@ -0,0 +1,355 @@
/*
* (c) Copyright, Real-Time Innovations, 2022.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef HTTP_CLIENT_CONNECTION_HPP
#define HTTP_CLIENT_CONNECTION_HPP
#include <array>
#include <chrono>
#include <memory>
#include <string>
#include <thread>
#include <civetweb.h>
#include "rti/apputils/ServiceException.hpp"
#include "rti/apputils/http/common/Response.hpp"
#include "rti/apputils/http/common/HttpUtils.hpp"
#include "rti/apputils/http/log/Log.hpp"
#include "Request.hpp"
namespace rti { namespace apputils { namespace http { namespace client {
/**
* @brief This class is a wrapper around the SSL configuration for the client.
*
*/
struct SslConfig {
SslConfig(
bool the_enabled = false,
std::string the_client_cert = "",
std::string the_server_cert = "")
: enabled(the_enabled),
client_cert(the_client_cert),
server_cert(the_server_cert)
{ }
bool enabled;
std::string client_cert;
std::string server_cert;
};
/**
* @brief This class deal with actual HTTP client requests and
* responses to and from CivetWeb.
*/
class Connection {
public:
/**
* @brief Construct a new Connection object Note that since every request
* reconnects the connection, we do not need to connect in the constructor
* itself.
*
* @param host Hostname of the server
* @param port Port of the server
* @param ssl_config SSL configuration for the client
*/
Connection(
const std::string& host = std::string("localhost"),
std::uint16_t port = HTTP_SERVER_DEFAULT_PORT,
SslConfig ssl_config = SslConfig())
: host_ { host }, port_ { port }, ssl_config_ { ssl_config }
{ }
/**
* @brief send an actual HTTP request to CivetWeb. It extracts all
* the information from the request and constructs a valid HTTP request
* string.
*
* @param request HTTP request to be sent to the server.
*/
void send_http_request(const Request& request)
{
connect();
// Send HTTP method and URL
auto mg_result = mg_printf(
connection_.get(),
"%s %s",
method_to_string(request.http_method()).c_str(),
request.uri());
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
mg_result > 0,
msg_client_error_printf);
// Add URL params to URL
if (request.uri_parameters().size() > 0) {
mg_result = mg_printf(connection_.get(), "?");
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
mg_result > 0,
msg_client_error_printf);
for (const auto& uri_param : request.uri_parameters()) {
std::array<char, MAX_URI_LENGTH> uri_param_value;
uri_param_value.fill('\0');
// We need to encode the URI params in case they contain
// spaces or any other character that may not be valid in a
// URL
mg_result = mg_url_encode(
uri_param.second.c_str(),
uri_param_value.data(),
MAX_URI_LENGTH);
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
mg_result > 0,
msg_client_error_url_encode);
mg_result = mg_printf(
connection_.get(),
"%s=%s&",
uri_param.first.c_str(),
uri_param_value.data());
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
mg_result > 0,
msg_client_error_printf);
}
}
// Specify HTTP version
mg_result = mg_printf(connection_.get(), " HTTP/%s\r\n", HTTP_VERSION);
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
mg_result > 0,
msg_client_error_printf);
// Add user-defined headers
for (const auto& header : request.headers()) {
mg_result = mg_printf(
connection_.get(),
"%s: %s\r\n",
header.first.c_str(),
header.second.c_str());
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
mg_result > 0,
msg_client_error_printf);
}
// Add default headers if not defined
if (request.header("Content-Length") == nullptr) {
mg_result = mg_printf(
connection_.get(),
"Content-Length: %llu\r\n",
static_cast<unsigned long long>(request.body().length()));
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
mg_result > 0,
msg_client_error_printf);
}
if (request.header("Content-Type") == nullptr) {
mg_result = mg_printf(
connection_.get(),
"Content-Type: %s\r\n",
HTTP_PLAIN_TEXT_CONTENT_TYPE);
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
mg_result > 0,
msg_client_error_printf);
}
if (request.header("Cache-Control") == nullptr) {
mg_result = mg_printf(
connection_.get(),
"Cache-Control: %s\r\n",
HTTP_CACHE_CONTROL_NO_CACHE);
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
mg_result > 0,
msg_client_error_printf);
}
if (request.header("Date") == nullptr) {
mg_result = mg_printf(
connection_.get(),
"Date: %s\r\n",
HttpSettings::get_current_date().c_str());
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
mg_result > 0,
msg_client_error_printf);
}
// Separate headers from body
mg_result = mg_printf(connection_.get(), "\r\n");
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
mg_result > 0,
msg_client_error_printf);
// Send body of the HTTP request -- if empty it will not write anything
mg_result = mg_printf(connection_.get(), "%s", request.body().c_str());
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
mg_result >= 0,
msg_client_error_printf);
}
/**
* @brief returns a Response based on the actual HTTP response received from
* CivetWeb. It extracts all the information originally stored in the
* mg_connection and transforms it into a valid Response object.
*
* @param timeout Number of milliseconds to wait for a response. A negative
* value means wait indefinitely.
*
* @return Response object with the response from CivetWeb.
*/
Response get_http_response(int timeout = HTTP_CLIENT_TIMEOUT_MS)
{
std::array<char, HTTP_BODY_INITIAL_LENGTH> error_buffer;
error_buffer[0] = '\0';
// Get CivetWeb's response.
const auto result = mg_get_response(
connection_.get(),
error_buffer.data(),
error_buffer.size(),
timeout);
// If we timeout trying to get the response, throw an exception.
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
result >= 0,
error_buffer.data());
// Convert CivetWeb's response into a Response
const mg_response_info *response_info =
mg_get_response_info(connection_.get());
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
response_info != nullptr,
msg_client_error_get_response_info);
return Response {
static_cast<unsigned short>(response_info->status_code),
response_info->status_text,
[&] {
const auto header =
mg_get_header(connection_.get(), "Content-Type");
return (header == nullptr) ? "" : header;
}(),
this->read_content(),
[&] {
std::unordered_map<std::string, std::string> headers;
for (int i = 0; i < response_info->num_headers; ++i) {
headers.emplace(
response_info->http_headers[i].name,
response_info->http_headers[i].value);
}
return headers;
}()
};
}
std::string read_content(std::size_t chunk_size = 256)
{
const auto response_info = mg_get_response_info(connection_.get());
if (response_info == nullptr) {
RTI_THROW_HTTP_EXCEPTION(
StatusCode::internal_server_error,
&RTI_LOG_ANY_FAILURE_s,
"Error reading content");
}
if (response_info->content_length >= 0) {
return read_content_with_content_length(
connection_.get(),
static_cast<std::size_t>(response_info->content_length));
}
return read_content_without_content_length(
connection_.get(),
chunk_size);
}
private:
/**
* @brief time we connect, we reset the mg_connection. This ensures
* that CivetWeb's internal buffers and state is cleaned up. This makes
* the Connection easier to use, since it does not need
* to be reconnected with every unit test run.
*/
void connect()
{
std::array<char, HTTP_BODY_INITIAL_LENGTH> error_buffer;
error_buffer[0] = 0;
if (ssl_config_.enabled) {
mg_client_options client_options = create_client_options();
connection_.reset(mg_connect_client_secure(
&client_options,
error_buffer.data(),
HTTP_BODY_INITIAL_LENGTH));
} else {
connection_.reset(mg_connect_client(
host_.c_str(),
port_,
ssl_config_.enabled,
error_buffer.data(),
HTTP_BODY_INITIAL_LENGTH));
}
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
connection_ != nullptr || error_buffer[0] == 0,
error_buffer.data());
}
/**
* @brief This method closes any existing connection.
*/
struct Disconnect {
void operator()(mg_connection *connection) const
{
mg_close_connection(connection);
}
};
/**
* @brief This method create a new mg_client_options object with the
* information provided by the user.
*/
mg_client_options create_client_options()
{
mg_client_options client_options;
client_options.host = host_.c_str();
client_options.port = port_;
client_options.client_cert =
(ssl_config_.client_cert.empty() || !ssl_config_.enabled)
? nullptr
: ssl_config_.client_cert.c_str();
client_options.server_cert =
(ssl_config_.server_cert.empty() || !ssl_config_.enabled)
? nullptr
: ssl_config_.server_cert.c_str();
client_options.host_name =
!ssl_config_.enabled ? nullptr : client_options.host;
return client_options;
}
private:
std::unique_ptr<mg_connection, Disconnect> connection_;
std::string host_;
std::uint16_t port_;
SslConfig ssl_config_;
};
}}}} // namespace rti::apputils::http::client
#endif // HTTP_CLIENT_CONNECTION_HPP
@@ -0,0 +1,202 @@
/*
* (c) Copyright, Real-Time Innovations, 2022.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef HTTP_CLIENT_REQUEST_HPP
#define HTTP_CLIENT_REQUEST_HPP
#include <array>
#include <string>
#include <cassert>
#include <cstring>
#include "rti/apputils/http/common/Method.hpp"
#include "rti/apputils/http/common/HttpUtils.hpp"
namespace rti { namespace apputils { namespace http { namespace client {
/**
* @brief Request class allow users to define their own requests in order to
* send them to the server trough the Connection class.
*/
class Request {
public:
/**
* @brief Construct a new Request empty object
*/
Request()
{
uri_.fill('\0');
}
/**
* @brief Getter for the HTTP method.
*
* @return Method object associated to the request.
*/
Method http_method() const
{
return http_method_;
}
/**
* @brief Setter for the HTTP method.
*
* @param http_method HTTP method (POST, PUT, GET, DELETE, HEAD, PATCH,
* OPTIONS).
* @return Reference to the Request object.
*/
Request& http_method(Method http_method)
{
http_method_ = http_method;
return *this;
}
/**
* @brief Gets the URL of the HTTP request.
*
* @return URL of the HTTP request.
*/
const char *uri() const
{
return uri_.data();
}
/**
* @brief Sets the URL of the HTTP request.
*
* @param uri URL of the HTTP request.
* @return Reference to the Request object.
*/
Request& uri(const char *uri)
{
std::copy(uri, uri + std::strlen(uri), uri_.data());
return *this;
}
/**
* @brief Sets the URL of the HTTP request.
*
* @param uri URL of the HTTP request.
* @return Reference to the Request object.
*/
Request& uri(const std::string& uri)
{
return this->uri(uri.c_str());
}
/**
* @brief Gets the value of an uri parameter.
*
* @param name Name of the parameter.
* @return const std::string*
*/
const std::string *uri_parameter(const std::string& name) const
{
const auto it = uri_parameters_.find(name);
if (it == uri_parameters_.end()) {
return nullptr;
}
return &it->second;
}
/**
* @brief Gets all the uri parameters.
*
* @return const StringMap&
*/
const StringMap& uri_parameters() const
{
return uri_parameters_;
}
/**
* @brief Sets all the uri parameters.
*
* @param uri_parameters StringMap with all the uri parameters.
* @return Request&
*/
Request& uri_parameters(const StringMap& uri_parameters)
{
uri_parameters_ = uri_parameters;
return *this;
}
/**
* @brief Gets the value of a header.\
*
* @param name Name of the parameter.
* @return const std::string*
*/
const std::string *header(const std::string& name) const
{
const auto it = headers_.find(name);
if (it == headers_.end()) {
return nullptr;
}
return &it->second;
}
/**
* @brief Gets all the headers.
*
* @return const StringMap&
*/
const StringMap& headers() const
{
return headers_;
}
/**
* @brief Sets all the headers.
*
* @param headers StringMap with all the headers.
* @return Request&
*/
Request& headers(const StringMap& headers)
{
headers_ = headers;
return *this;
}
/**
* @brief Gets the body of the request.
*
* @return const std::string&
*/
const std::string& body() const
{
return body_;
}
/**
* @brief Sets the body of the request.
*
* @param body Body of the request.
* @return Request&
*/
Request& body(const std::string& body)
{
body_ = body;
return *this;
}
private:
Method http_method_ = Method::http_get;
std::array<char, MAX_URI_LENGTH> uri_;
StringMap uri_parameters_;
StringMap headers_;
std::string body_;
};
}}}} // namespace rti::apputils::http::client
#endif // HTTP_CLIENT_REQUEST_HPP
@@ -0,0 +1,177 @@
/*
* (c) Copyright, Real-Time Innovations, 2020.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef CIVETWEBSOCKET_TEST_CONNECTION_CLIENT_H
#define CIVETWEBSOCKET_TEST_CONNECTION_CLIENT_H
#include <memory>
#include <mutex>
#include "civetweb.h"
#include <queue>
#include <rti/apputils/http/server/CivetWebSocketConnection.hpp>
#include <rti/apputils/log/LogConfig.hpp>
#include "log/LogMsg.hpp"
// #include "utils/WebDDSUtils.h"
#define CIVETWEB_CLIENT_TIMEOUT_MS 1000
#define WEBSOCKET_ERROR_BUFFER_SIZE 10000
#define WEBSOCKETinput_buffer__SIZE 10000
#define WEBSERVER_DEFAULT_PORT 19020
namespace rti { namespace apputils { namespace http { namespace client {
namespace test {
class CivetWebSocketClient {
public:
~CivetWebSocketClient()
{
mg_close_connection(conn_);
}
static int websocket_client_data_handler(
struct mg_connection * /*conn*/,
int flags,
char *data,
size_t data_len,
void *user_data)
{
CivetWebSocketClient *user = NULL;
if (user_data == NULL) {
SERVICELog_exception(
&RTI_LOG_ANY_FAILURE_s,
"No user data defined");
return 1;
}
switch (((unsigned) flags & 0xFu)) {
case MG_WEBSOCKET_OPCODE_TEXT:
user = reinterpret_cast<CivetWebSocketClient *>(user_data);
user->receive(std::string(data, data_len));
break;
default:
SERVICELog_exception(
&RTI_LOG_ANY_FAILURE_s,
"The websocket message code received is not recognize");
break;
}
return 1;
}
static void websocket_client_close_handler(
const struct mg_connection *conn,
void *user_data)
{
struct mg_context *context = mg_get_context(conn);
if (user_data == NULL) {
SERVICELog_exception(
&RTI_LOG_ANY_FAILURE_s,
"No user data defined");
return;
}
mg_lock_context(context);
mg_set_user_connection_data((struct mg_connection *) conn, NULL);
mg_unlock_context(context);
}
static std::shared_ptr<CivetWebSocketClient> connect(
const char *host = "localhost",
int port = WEBSERVER_DEFAULT_PORT,
bool use_ssl = false,
const char *path = "/dds/websocket/test")
{
StringBuffer error_buffer(WEBSOCKET_ERROR_BUFFER_SIZE);
auto result = std::make_shared<CivetWebSocketClient>();
struct mg_connection *conn = mg_connect_websocket_client(
host,
port,
use_ssl,
(char *) error_buffer.c_str_ptr(),
WEBSOCKET_ERROR_BUFFER_SIZE,
path,
NULL,
websocket_client_data_handler,
websocket_client_close_handler,
result.get());
if (conn == NULL) {
SERVICELog_exception(&RTI_LOG_ANY_FAILURE_s, "No Connection");
result.reset();
return result;
}
result->connection(conn);
return result;
}
public:
CivetWebSocketClient() = default;
CivetWebSocketClient(mg_connection *conn)
: conn_(conn)
{ }
void send(const StringBuffer& msg)
{
mg_websocket_client_write(
conn_,
MG_WEBSOCKET_OPCODE_TEXT,
msg.c_str(),
msg.length());
}
void request_close()
{
mg_websocket_client_write(
conn_,
MG_WEBSOCKET_OPCODE_CONNECTION_CLOSE,
NULL,
0);
}
void receive(const StringBuffer& buffer)
{
std::lock_guard<std::recursive_mutex> guard(mutex_);
received_buffer_.push(buffer);
}
bool buffer(StringBuffer& buffer)
{
std::lock_guard<std::recursive_mutex> guard(mutex_);
bool received = false;
if (!received_buffer_.empty()) {
buffer = received_buffer_.front();
received_buffer_.pop();
received = true;
}
return received;
}
void connection(mg_connection *conn)
{
conn_ = conn;
}
private:
mg_connection *conn_ = nullptr;
std::queue<StringBuffer> received_buffer_;
std::recursive_mutex mutex_;
};
}}}}} // namespace rti::apputils::http::client::test
#endif
@@ -0,0 +1,74 @@
/*
* (c) Copyright, Real-Time Innovations, 2022.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef HTTP_EXCEPTION_HPP
#define HTTP_EXCEPTION_HPP
// RTI headers
#include "rti/apputils/http/common/StatusCode.hpp"
#include "rti/apputils/ServiceException.hpp"
namespace rti { namespace apputils { namespace http {
/**
* @brief Exception class for HTTP errors that inherit from ServiceException
*/
class HttpException : public ServiceException {
public:
/**
* @brief Constructor
*
* @param throwing_context The context where the exception was thrown
* @param status_code HTTP status code
* @param log_message RTILog message (it could be a format string)
*/
template <typename... Args>
HttpException(
const char *throwing_context,
StatusCode error_code,
const RTILogMessage *log_message,
Args... args)
: ServiceException(throwing_context, log_message, args...),
error_code_(error_code)
{ }
/**
* @brief Constructor
*
* @param ServiceException Service Exception to build the HttpException
* @param status_code HTTP status code
*/
HttpException(
const ServiceException& e,
StatusCode error_code)
: ServiceException(e),
error_code_(error_code)
{ }
/**
* @brief Get the HTTP status code
*
* @return HTTP status code
*/
StatusCode error_code() const
{
return error_code_;
}
private:
StatusCode error_code_;
};
}}} // namespace rti::apputils::http
#define RTI_THROW_HTTP_EXCEPTION(...) \
throw rti::apputils::http::HttpException(RTI_FUNCTION_NAME, __VA_ARGS__)
#endif // HTTP_EXCEPTION_HPP
@@ -0,0 +1,167 @@
/*
* (c) Copyright, Real-Time Innovations, 2022.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef HTTP_UTILS_H
#define HTTP_UTILS_H
#include <ctime>
#include <cstring>
#include <unordered_map>
#include <civetweb.h>
#include <reda/reda_string.h>
#include <dds_c/dds_c_printformat.h>
#include "HttpException.hpp"
#include "StatusCode.hpp"
namespace rti { namespace apputils { namespace http {
using StringMap = std::unordered_map<std::string, std::string>;
// Unfortunately Civetweb doesn't expose these constants, so we needed to
// copy the values from civetweb.c (static struct mg_option config_options[])
constexpr auto HTTP_CLIENT_TIMEOUT_MS = int { 1000 };
constexpr auto HTTP_SERVER_DEFAULT_PORT = int { 8080 };
constexpr auto HTTP_SERVER_DEFAULT_PORT_STR = "8080";
constexpr auto HTTP_SERVER_DEFAULT_HOST = "127.0.0.1";
constexpr auto HTTP_VERSION = "1.1";
constexpr auto HTTP_CACHE_CONTROL_NO_CACHE = "no-cache";
constexpr auto HTTP_BODY_INITIAL_LENGTH = std::size_t { 1000 };
constexpr auto HTTP_PLAIN_TEXT_CONTENT_TYPE = "text/plain";
constexpr auto HTTP_JSON_CONTENT_TYPE = "application/json";
constexpr auto HTTP_XML_CONTENT_TYPE = "application/xml";
constexpr auto HTTP_CONTENT_TYPE_HEADER = "Content-Type";
constexpr auto HTTP_DATE_STR_LENGTH = std::size_t { 64 };
/**
* Maximum URL length we handle is 2048. This value is based on the
* comments on this article http://stackoverflow.com/questions/417142
*/
constexpr auto MAX_URI_LENGTH = int { 2048 };
/**
* @brief This structure contains utility methods to generate HttpSettings that
* need to be set as part of the HTTP response.
*/
struct HttpSettings {
/**
* @brief This method converts time_t to a string. According to RFC2616,
* Sec 14.18, this must be included in all responses other than
* 100, 101, 5xx.
*
* @return A string containing the date in the format specified by RFC2616.
*/
static std::string get_current_date()
{
const auto curtime = std::time(nullptr);
if (curtime == (time_t) -1) {
return "Thu, 01 Jan 1970 00:00:00 GMT";
}
struct tm curr_tm;
#ifdef RTI_WIN32
if (gmtime_s(&curr_tm, &curtime) != 0) {
return "Thu, 01 Jan 1970 00:00:00 GMT";
}
#else
if (gmtime_r(&curtime, &curr_tm) != &curr_tm) {
return "Thu, 01 Jan 1970 00:00:00 GMT";
}
#endif
auto result = std::string(HTTP_DATE_STR_LENGTH, '\0');
const auto bytes_written = std::strftime(
&result[0],
result.size(),
"%a, %d %b %Y %H:%M:%S GMT",
&curr_tm);
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
bytes_written > 0,
"Error in std::strftime");
result.resize(bytes_written);
return result;
}
};
inline std::string read_content_with_content_length(
mg_connection *connection,
std::size_t content_length)
{
auto content = std::string(content_length, 0);
auto result = 0;
while (content_length > 0 && result >= 0) {
result = mg_read(connection,
&content[content.size() - content_length],
content_length);
content_length -= result;
}
return content;
}
inline std::string read_content_without_content_length(
mg_connection *connection,
std::size_t chunk_size = 256)
{
auto content = std::string();
auto bytes_read = std::size_t { 0 };
auto result = 0;
do {
bytes_read += result;
content.resize(bytes_read + chunk_size);
result = mg_read(connection, &content[bytes_read], chunk_size);
} while (result > 0);
content.resize(bytes_read);
return content;
}
inline std::string base64_encode(const std::string& in)
{
std::string out;
out.reserve(in.size() * 2);
const char *b64 =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
for (std::size_t i = 0; i < in.size(); i += 3) {
// Static cast to unsigned char to avoid cert_str34_c_violation
int a = static_cast<unsigned char>(in[i]);
int b = i + 1 >= in.size() ? 0 : static_cast<unsigned char>(in[i + 1]);
int c = i + 2 >= in.size() ? 0 : static_cast<unsigned char>(in[i + 2]);
out.push_back(b64[a >> 2]);
out.push_back(b64[((a & 3) << 4) | (b >> 4)]);
if (i + 1 < in.size()) {
out.push_back(b64[(b & 15) << 2 | (c >> 6)]);
}
if (i + 2 < in.size()) {
out.push_back(b64[c & 63]);
}
}
while (out.size() % 4 != 0) {
out.push_back('=');
}
out.shrink_to_fit();
return out;
}
}}} // namespace rti::apputils::http
#endif // HTTP_UTILS_H
@@ -0,0 +1,96 @@
/*
* (c) Copyright, Real-Time Innovations, 2022.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef HTTP_METHOD_HPP
#define HTTP_METHOD_HPP
#include <string>
#include "rti/apputils/http/common/HttpException.hpp"
namespace rti {
namespace apputils { namespace http {
/**
* Enum class with every HTTP method
*/
enum class Method {
http_post,
http_put,
http_get,
http_delete,
http_options,
http_head,
http_patch
};
/**
* @brief Convert a Method enum to an std::string
*
* @param http_method Method enum to convert
* @return std::string with the method name
*/
inline std::string method_to_string(Method http_method)
{
switch (http_method) {
case Method::http_post:
return "POST";
case Method::http_put:
return "PUT";
case Method::http_get:
return "GET";
case Method::http_delete:
return "DELETE";
case Method::http_options:
return "OPTIONS";
case Method::http_head:
return "HEAD";
case Method::http_patch:
return "PATCH";
default:
RTI_THROW_HTTP_EXCEPTION(
StatusCode::bad_request,
&RTI_LOG_ANY_FAILURE_s,
"Invalid HTTP method");
}
}
/**
* @brief Convert a std::string to a Method enum
*
* @param http_method String to convert
* @return Method enum
*/
inline Method method_from_string(const std::string& http_method)
{
if (http_method == "POST") {
return Method::http_post;
} else if (http_method == "PUT") {
return Method::http_put;
} else if (http_method == "GET") {
return Method::http_get;
} else if (http_method == "DELETE") {
return Method::http_delete;
} else if (http_method == "OPTIONS") {
return Method::http_options;
} else if (http_method == "HEAD") {
return Method::http_head;
} else if (http_method == "PATCH") {
return Method::http_patch;
} else {
RTI_THROW_HTTP_EXCEPTION(
StatusCode::bad_request,
&RTI_LOG_ANY_FAILURE_s,
"Invalid HTTP method");
}
}
}}} // namespace rti::apputils::http
#endif // HTTP_METHOD_HPP
@@ -0,0 +1,51 @@
/*
* (c) Copyright, Real-Time Innovations, 2022.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef HTTP_RESPONSE_HPP
#define HTTP_RESPONSE_HPP
#include <string>
#include <unordered_map>
namespace rti { namespace apputils { namespace http {
/**
* @brief This class contains all the elements involved in an HTTP response
*/
struct Response {
/**
* Response's status code
*/
unsigned short http_status_code;
/**
* Response's reason phrase
*/
std::string http_reason_phrase;
/**
* Response's content type
*/
std::string http_content_type;
/**
* Response's body
*/
std::string http_content;
/**
* Response's headers as a map
*/
std::unordered_map<std::string, std::string> http_headers;
};
}}} // namespace rti::apputils::http
#endif // HTTP_RESPONSE_HPP
@@ -0,0 +1,90 @@
/*
* (c) Copyright, Real-Time Innovations, 2022.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef HTTP_STATUS_CODE_HPP
#define HTTP_STATUS_CODE_HPP
#include <string>
namespace rti { namespace apputils { namespace http {
/**
* @brief Enum class with HTTP status codes.
*/
enum class StatusCode {
ok = 200,
created = 201,
accepted = 202,
no_content = 204,
multiple_choices = 300,
moved_permanently = 301,
moved_temporarily = 302,
not_modified = 304,
bad_request = 400,
unauthorized = 401,
forbidden = 403,
not_found = 404,
not_allowed = 405,
internal_server_error = 500,
not_implemented = 501,
bad_gateway = 502,
service_unavailable = 503
};
/**
* @brief Convert a StatusCode enum to an std::string
*
* @param status_code StatusCode enum to convert
* @return std::string with the status code name
*/
inline std::string status_code_to_string(StatusCode status_code)
{
switch (status_code) {
case StatusCode::ok:
return "OK";
case StatusCode::created:
return "Created";
case StatusCode::accepted:
return "Accepted";
case StatusCode::no_content:
return "No Content";
case StatusCode::multiple_choices:
return "Multiple Choices";
case StatusCode::moved_permanently:
return "Moved Permanently";
case StatusCode::moved_temporarily:
return "Moved Temporarily";
case StatusCode::not_modified:
return "Not Modified";
case StatusCode::bad_request:
return "Bad Request";
case StatusCode::unauthorized:
return "Unauthorized";
case StatusCode::forbidden:
return "Forbidden";
case StatusCode::not_found:
return "Not Found";
case StatusCode::not_allowed:
return "Not Allowed";
case StatusCode::internal_server_error:
return "Internal Server Error";
case StatusCode::not_implemented:
return "Not Implemented";
case StatusCode::bad_gateway:
return "Bad Gateway";
case StatusCode::service_unavailable:
return "Service Unavailable";
default:
return "";
}
}
}}} // namespace rti::apputils::http
#endif // HTTP_STATUS_CODE_HPP
@@ -0,0 +1,100 @@
/*
* (c) Copyright, Real-Time Innovations, 2024.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef WEBSOCKET_API_UTILS_H_
#define WEBSOCKET_API_UTILS_H_
#include "dds_c/dds_c_printformat.h"
#include <memory>
#include <rti/apputils/http/common/HttpException.hpp>
#define ERR_LOST_HEADER "Lost header"
#define WEBSOCKET_RESPONSE_NAME "response"
#define WEBSOCKET_HELLO_FAIL_HEADER "HELLO FAIL:"
namespace rti { namespace apputils { namespace websocket {
enum class WebSocketStateKind{
UNKNOWN,
CREATE,
OPEN,
READY,
CLOSE
};
enum class WebSocketContentType{
UNKNOWN,
JSON,
XML
};
inline DDS_PrintFormatKind to_print_format_kind(WebSocketContentType contentType) {
return contentType == WebSocketContentType::JSON
? DDS_JSON_PRINT_FORMAT
: DDS_XML_PRINT_FORMAT;
}
inline WebSocketContentType to_websocket_content_type(DDS_PrintFormatKind formatKind) {
WebSocketContentType value = WebSocketContentType::UNKNOWN;
switch (formatKind) {
case DDS_JSON_PRINT_FORMAT:
value = WebSocketContentType::JSON;
break;
case DDS_XML_PRINT_FORMAT:
value = WebSocketContentType::XML;
break;
case DDS_DEFAULT_PRINT_FORMAT:
value = WebSocketContentType::XML;
break;
default:
value = WebSocketContentType::UNKNOWN;
break;
}
return value;
}
/**
* The RequestException class defines a exception will log an error message
* via RTIWsddsLog_exeption or will just propagate a given return code
* identifying the error and an error message. The class inherits from
* Exception, as a result users can use familiar interfaces, such as what() to
* get the error message. Also it have the request id to create the error
* message to be sent.
*/
class RequestException : public rti::apputils::http::HttpException {
public:
RequestException(
const char *throwing_context,
rti::apputils::http::StatusCode error_code,
const RTILogMessage *log_message,
const char *method_name,
...)
: HttpException(throwing_context, error_code, log_message),
method_name_(method_name)
{
}
/**
* Returns the method name that threw the exception.
* @return Method name that threw the exception.
*/
const char * get_method_name() const
{
return method_name_;
}
private:
const char *method_name_;
};
}}} // namespace rti::webdds::websocket
#endif // WEBSOCKET_API_UTILS_H_
@@ -0,0 +1,41 @@
/*
* (c) Copyright, Real-Time Innovations, 2022.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef HTTP_LOG_HPP
#define HTTP_LOG_HPP
#include "log/log_common.h"
#include "log.peer.h"
#include "apputils/apputils_log.h"
#define APPUTILS_CURRENT_SUBMODULE SUBMODULE_APPUTILS_HTTP
const struct RTILogMessage APPUTILS_LOG_HTTP_EXAMPLE_sss= {
PRODUCT_WAVEWORKS|MODULE_APPUTILS|SUBMODULE_APPUTILS_HTTP|0,
"%s: %s: %s"
};
const struct RTILogMessage APPUTILS_LOG_HTTP_SENT_RESPONSE_us= {
PRODUCT_WAVEWORKS|MODULE_APPUTILS|SUBMODULE_APPUTILS_HTTP|1,
"Sent response %u '%s'\n"
};
constexpr auto msg_client_error_printf = "Client: error at mg_printf";
constexpr auto msg_client_error_url_encode = "Client: error at mg_url_encode";
constexpr auto msg_client_error_get_response_info =
"Client: error at mg_get_response_info";
constexpr auto msg_server_error_printf = "Server: error at mg_printf";
constexpr auto msg_server_error_read = "Server: error at mg_read";
constexpr auto msg_server_error_send_http_error = "Server: error at mg_send_http_error";
constexpr auto msg_server_error_get_request_info = "Server: error at mg_get_request_info";
constexpr auto msg_server_error_get_header = "Server: error at mg_get_header";
#endif // HTTP_UTILS_HPP
@@ -0,0 +1,84 @@
/*
* (c) Copyright, Real-Time Innovations, 2024.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef CIVETWEBSOCKET_CONNECTION_H
#define CIVETWEBSOCKET_CONNECTION_H
#include <civetweb.h>
#include "WebSocketConnection.hpp"
namespace rti { namespace apputils { namespace websocket { namespace civetweb {
class CivetWebSocketConnection : public WebSocketConnection {
public:
explicit CivetWebSocketConnection(struct mg_connection *conn) : conn_(conn)
{
}
void send(const StringBuffer& msg)
{
struct mg_context *context = mg_get_context(conn_);
mg_lock_context(context);
mg_websocket_write(
conn_,
MG_WEBSOCKET_OPCODE_TEXT,
msg.c_str(),
msg.length());
mg_unlock_context(context);
}
void send_pong(const StringBuffer& msg)
{
mg_context *context = mg_get_context(conn_);
mg_lock_context(context);
mg_websocket_write(
conn_,
MG_WEBSOCKET_OPCODE_PONG,
msg.c_str(),
msg.length());
mg_unlock_context(context);
}
bool operator==(WebSocketConnection* conn)
{
return conn_ == static_cast<CivetWebSocketConnection *>(conn)->conn();
}
const struct mg_connection* conn() const
{
return conn_;
}
void close()
{
struct mg_context *context = mg_get_context(conn_);
mg_lock_context(context);
mg_close_connection(conn_);
mg_unlock_context(context);
}
private:
mg_connection* conn_;
};
}}}} // namespace rti::apputils::websocket::civetweb
#endif // CIVETWEBSOCKET_CONNECTION_H
@@ -0,0 +1,292 @@
/*
* (c) Copyright, Real-Time Innovations, 2022.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef HTTP_SERVER_CONNECTION_HPP
#define HTTP_SERVER_CONNECTION_HPP
#include <CivetServer.h>
#include <civetweb.h>
#include <nlohmann/json.hpp>
#include "rti/apputils/http/common/HttpUtils.hpp"
#include "rti/apputils/http/common/Response.hpp"
#include "rti/apputils/http/log/Log.hpp"
#include "log.peer.h"
#include "Request.hpp"
namespace rti { namespace apputils { namespace http { namespace server {
/**
* @brief This class implements the interfaces defined by rest::Connection to
* deal with CivetWeb's native connections.
*/
class Connection {
public:
/**
* @brief Constructor of the Connection class. It saves a reference to
* CivetWeb's native connection. It also constructs a Request from
* the connection passed by parameter.
*
* @param conn CivetWeb's connection that is wrapped in this class.
*/
explicit Connection(mg_connection *conn) : conn_(conn), request_(conn_)
{ }
/**
* Destructor of the Connection class.
*/
~Connection()
{ }
/**
* @brief This method returns the HTTP request.
*
* @return The HTTP Request.
*/
const Request& request() const
{
return request_;
}
/**
* @brief This method returns the CivetWeb's native connection.
*
* @return The CivetWeb's native connection.
*/
mg_connection *connection() const
{
return conn_;
}
/**
* @brief This method sends an "Error 501: Not Implemented" response.
*/
void send_not_implemented(std::string const& message = std::string())
{
auto response = Response();
response.http_content_type = HTTP_JSON_CONTENT_TYPE;
response.http_status_code =
static_cast<unsigned short>(StatusCode::not_implemented);
response.http_reason_phrase =
status_code_to_string(StatusCode::not_implemented);
response.http_content = message;
this->send_http_response(response);
}
/**
* @brief This method sends an "OK 204: No Content" response.
*/
void send_http_ok_no_content()
{
auto response = Response();
response.http_content_type = HTTP_JSON_CONTENT_TYPE;
response.http_status_code =
static_cast<unsigned short>(StatusCode::no_content);
response.http_reason_phrase =
status_code_to_string(StatusCode::no_content);
this->send_http_response(response);
}
/**
* @brief This method sends an "Error 400: Bad Request" response.
*/
void send_http_bad_request(std::string const& message = std::string())
{
auto response = Response();
response.http_content_type = HTTP_JSON_CONTENT_TYPE;
response.http_status_code =
static_cast<unsigned short>(StatusCode::bad_request);
response.http_reason_phrase =
status_code_to_string(StatusCode::bad_request);
response.http_content = message;
this->send_http_response(response);
}
/**
* @brief This method sends an "Error 404: Not Found" response.
*/
void send_http_not_found(std::string const& message = std::string())
{
auto response = Response();
response.http_content_type = HTTP_JSON_CONTENT_TYPE;
response.http_status_code =
static_cast<unsigned short>(StatusCode::not_found);
response.http_reason_phrase =
status_code_to_string(StatusCode::not_found);
response.http_content = message;
this->send_http_response(response);
}
/**
* @brief This method sends an "Error 405: Method Not Allowed" response.
*/
void send_http_method_not_allowed(
std::string const& message = std::string())
{
auto response = Response();
response.http_content_type = HTTP_JSON_CONTENT_TYPE;
response.http_status_code =
static_cast<unsigned short>(StatusCode::not_allowed);
response.http_reason_phrase =
status_code_to_string(StatusCode::not_allowed);
response.http_content = message;
this->send_http_response(response);
}
/**
* @brief This method sends an "OK 202: Accepted" response.
*/
void send_http_accepted(std::string const& message = std::string())
{
auto response = Response();
response.http_content_type = HTTP_JSON_CONTENT_TYPE;
response.http_status_code =
static_cast<unsigned short>(StatusCode::accepted);
response.http_reason_phrase =
status_code_to_string(StatusCode::accepted);
response.http_content = message;
this->send_http_response(response);
}
/**
* @brief This method sends an "OK 200: OK" response.
*/
void send_http_ok(std::string const& message = std::string())
{
auto response = Response();
response.http_content_type = HTTP_JSON_CONTENT_TYPE;
response.http_status_code = static_cast<unsigned short>(StatusCode::ok);
response.http_reason_phrase = status_code_to_string(StatusCode::ok);
response.http_content = message;
this->send_http_response(response);
}
/**
* @brief This operation constructs an HTTP response given its definition in
* Response format. Once the Response is constructed, is sent via CivetWeb's
* native APIs.
*/
void send_http_response(const Response& rpns)
{
// Constructs headers and body of the HTTP response.
auto mg_result = mg_printf(
conn_,
"HTTP/%s %u %s\r\n"
"Content-Type: %s\r\n"
"Cache-Control: %s\r\n"
"Content-Length: %lu\r\n"
"Access-Control-Allow-Origin: *\r\n"
"Date: %s\r\n\r\n"
"%s",
HTTP_VERSION,
rpns.http_status_code,
rpns.http_reason_phrase.c_str(),
rpns.http_content_type.c_str(),
HTTP_CACHE_CONTROL_NO_CACHE,
rpns.http_content.length(),
HttpSettings::get_current_date().c_str(),
rpns.http_content.c_str());
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
mg_result >= 0,
msg_server_error_printf);
APPUTILSLogMessage_log(
RTI_LOG_BIT_LOCAL,
&APPUTILS_LOG_HTTP_SENT_RESPONSE_us,
rpns.http_status_code,
rpns.http_reason_phrase.c_str());
}
/**
* @brief Prepares and send the body of an HTTP response given an exception.
*
* @param [in] e Exception to be formatted and sent.
*/
void send_error_exception_response(const HttpException& e)
{
auto response = Response();
response.http_content_type = HTTP_JSON_CONTENT_TYPE;
response.http_status_code = static_cast<unsigned short>(e.error_code());
response.http_reason_phrase = status_code_to_string(e.error_code());
nlohmann::json http_content_json;
http_content_json["code"] = status_code_to_string(e.error_code());
http_content_json["message"] = e.what();
response.http_content = http_content_json.dump();
this->send_http_response(response);
}
/**
* @brief Read the content of an HTTP request.
*
* @param chunk_size size of the chunk to read.
* @return std::string with the read content.
*/
std::string read_content(std::size_t chunk_size = 256)
{
const auto request_info = mg_get_request_info(conn_);
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
request_info != nullptr,
msg_server_error_get_request_info);
if (request_info->content_length >= 0) {
return read_content_with_content_length(
conn_,
static_cast<std::size_t>(request_info->content_length));
}
return read_content_without_content_length(conn_, chunk_size);
}
/**
* @brief Check that content type is the expected one.
*
* @param expected_content_type
* @return true if the content type is the expected one. otherwise
* false.
*/
bool check_content_type(const std::string& expected_content_type)
{
auto ret = true;
// Check if Content-Type is application/json
if (this->request().get_header(
rti::apputils::http::HTTP_CONTENT_TYPE_HEADER)
!= expected_content_type) {
ret = false;
}
return ret;
}
private:
mg_connection *conn_;
Request request_;
};
}}}} // namespace rti::apputils::http::server
#endif // HTTP_SERVER_CONNECTION_HPP
@@ -0,0 +1,232 @@
/*
* (c) Copyright, Real-Time Innovations, 2022.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef HTTP_SERVER_REQUEST_HPP
#define HTTP_SERVER_REQUEST_HPP
#include <string>
#include <cstring>
#include <CivetServer.h>
#include <civetweb.h>
#include "rti/apputils/StringBuffer.hpp"
#include "rti/apputils/http/common/Method.hpp"
#include "rti/apputils/http/common/HttpException.hpp"
namespace rti { namespace apputils { namespace http { namespace server {
/**
* @brief This class implements the Request to handle Requests, that is
* requests provided by the CivetWeb server.
*/
class Request {
public:
/**
* @brief Constructor of the Request. Retrieves information from the
* mg_request_info provided by CivetWeb's connection.
*
* @param conn Civetweb's connection.
*/
explicit Request(mg_connection *conn) : conn_(conn)
{ }
/**
* @brief Destructor of the Request class.
*/
~Request()
{ }
/**
* @brief Returns the URL of CivetWeb's request.
*
* @return URL of the HTTP request.
*/
const char *uri() const
{
const auto req = mg_get_request_info(conn_);
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
req != nullptr,
msg_server_error_get_request_info);
return req->local_uri;
}
/**
* @brief Gets a URI parameter given its name.
*
* @param value The value of the parameter.
* @param name of the parameter to be retrieved.
* @return Indicates whether the parameter was found.
*/
bool uri_param(std::string& value, const std::string& name) const
{
const auto req = mg_get_request_info(conn_);
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
req != nullptr,
msg_server_error_get_request_info);
if (req->query_string == nullptr) {
return false;
}
return CivetServer::getParam(req->query_string, name.c_str(), value);
}
/**
* @brief Constructs a map with the query parameters in a given URI.
*
* @param params Map with decoded names and values of all the query
* parameters in the URI.
* @return Boolean value indicating whether query parameters were retrieved
* or not.
*/
bool uri_params(StringMap& params) const
{
const auto req = mg_get_request_info(conn_);
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
req != nullptr,
msg_server_error_get_request_info);
if (req->query_string == nullptr) {
return false;
}
const auto query_string = req->query_string;
auto query_string_length = strlen(query_string);
auto i = std::size_t { 0 };
while (i < query_string_length) {
// Get Name
auto i0 = i;
while ((i < query_string_length) && query_string[i] != '='
&& query_string[i] != '&') {
i++;
}
auto name = std::string();
CivetServer::urlDecode(&query_string[i0], i - i0, name, true);
if (i < query_string_length && query_string[i] == '=') {
i++;
}
// Get Value
i0 = i;
while ((i < query_string_length) && query_string[i] != '&') {
i++;
}
auto value = std::string();
CivetServer::urlDecode(&query_string[i0], i - i0, value, true);
if (i < query_string_length && query_string[i] == '&') {
i++;
}
// Add key-value pair to the map
params[name] = value;
}
return true;
}
/**
* @brief Gets a URI header given its name.
*
* @param Name of the header to be retrieved.
* @return Gets the value of an HTTP header given its name.
*/
const char *get_header(const char *name) const
{
const char *value = mg_get_header(conn_, name);
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
value != nullptr,
msg_server_error_get_header);
return value;
}
/**
* @brief This method saves the body of the HTTP request in the body buffer.
*
* @param [out] Body of the HTTP request.
* @param [in,out] Length of the body of the HTTP request.
*/
void get_body(char *& body, size_t& body_length) const
{
auto mg_result = mg_read(conn_, body, body_length);
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
mg_result >= 0,
msg_server_error_read);
}
/**
* @brief Saves the body of the HTTP request in a string buffer.
*
* @param [out] Body of the HTTP request in StringBuffer.
*/
void get_body(StringBuffer& body) const
{
const auto body_length = get_body_length();
body.ensure_capacity(body_length + 1);
auto mg_result = mg_read(conn_, body.c_str_ptr(), body_length);
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
mg_result >= 0,
msg_server_error_read);
}
/**
* @brief Returns the length of the body of the HTTP request.
*
* @return Body length.
*/
size_t get_body_length() const
{
const auto req = mg_get_request_info(conn_);
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
req != nullptr,
msg_server_error_get_request_info);
auto content_length = req->content_length;
// Check that the content_length is not negative (except for GET,
// DELETE, and PUT operations) where it is permitted.
if ((content_length < 0) && (strcmp(req->request_method, "GET") != 0)
&& (strcmp(req->request_method, "DELETE") != 0)
&& (strcmp(req->request_method, "PUT") != 0)) {
RTI_THROW_HTTP_EXCEPTION(
StatusCode::bad_request,
&RTI_LOG_ANY_FAILURE_s,
"Content-length of the HTTP request is invalid.");
}
return content_length < 0 ? 0 : static_cast<size_t>(content_length);
}
/**
* @brief Returns the method of the HTTP request.
*
* @return Method of the HTTP request.
*/
Method get_http_method() const
{
const struct mg_request_info *req = mg_get_request_info(conn_);
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
req != nullptr,
msg_server_error_get_request_info);
return method_from_string(
req->request_method == nullptr ? "" : req->request_method);
}
private:
struct mg_connection *conn_;
};
}}}} // namespace rti::apputils::http::server
#endif // HTTP_SERVER_REQUEST_HPP
@@ -0,0 +1,157 @@
/*
* (c) Copyright, Real-Time Innovations, 2022.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef HTTP_SERVER_REQUEST_HANDLER_HPP
#define HTTP_SERVER_REQUEST_HANDLER_HPP
#include <functional>
#include "rti/apputils/http/common/Method.hpp"
#include "rti/apputils/http/common/HttpUtils.hpp"
#include "Connection.hpp"
#include "ServerProperty.hpp"
namespace rti { namespace apputils { namespace http { namespace server {
// Forward declaration
class Server;
/**
* @brief This class defines a generic interface to be implemented by each
* RequestHandler. This implies implementing a set or methods to handle
* different types of requests (e.g., POST, PUT, GET, DELETE, OPTIONS, HEAD and
* PATCH).
*/
class RequestHandler {
public:
/**
* @brief Virtual destructor of the RequestHandler class.
*/
virtual ~RequestHandler()
{ }
/**
* @brief The POST request handler is an interface to implement a method
* that addresses POST requests and generates responses.
*
* @param server Reference to the wrapper of the internal server API.
* @param connection
* @param web_server_property Reference to the web server properties.
*/
virtual void handle_post(
Server&,
Connection& connection,
const ServerProperty&)
{
connection.send_not_implemented();
}
/**
* @brief The PUT request handler is an interface to implement a method that
* addresses PUT requests and generates responses.
*
* @param server Reference to the wrapper of the internal server API.
* @param connection
* @param web_server_property Reference to the service properties.
*/
virtual void handle_put(
Server&,
Connection& connection,
const ServerProperty&)
{
connection.send_not_implemented();
}
/**
* @brief The GET request handler is an interface to implement a method that
* addresses GET requests and generates responses.
*
* @param server Reference to the wrapper of the internal server API.
* @param connection
* @param web_server_property Reference to the service properties.
*/
virtual void handle_get(
Server&,
Connection& connection,
const ServerProperty&)
{
connection.send_not_implemented();
}
/**
* @brief The DELETE request handler is an interface to implement a method
* that addresses DELETE requests and generates responses.
*
* @param server Reference to the wrapper of the internal server API.
* @param connection
* @param web_server_property Reference to the web server properties.
*/
virtual void handle_delete(
Server&,
Connection& connection,
const ServerProperty&)
{
connection.send_not_implemented();
}
/**
* @brief The OPTIONS request handler is an interface to implement a method
* that addresses OPTIONS requests and generates responses.
*
* @param server Reference to the wrapper of the internal server API.
* @param connection
* @param web_server_property Reference to the web server properties.
*/
virtual void handle_options(
Server&,
Connection& connection,
const ServerProperty&)
{
connection.send_not_implemented();
}
/**
* @brief The HEAD request handler is an interface to implement a method
* that addresses HEAD requests and generates responses.
*
* @param server Reference to the wrapper of the internal server API.
* @param connection
* @param web_server_property Reference to the web server properties.
*/
virtual void handle_head(
Server&,
Connection& connection,
const ServerProperty&)
{
connection.send_not_implemented();
}
/**
* @brief The PATCH request handler is an interface to implement a method
* that addresses PATCH requests and generates responses.
*
* @param server Reference to the wrapper of the internal server API.
* @param connection
* @param web_server_property Reference to the web server properties.
*/
virtual void handle_patch(
Server&,
Connection& connection,
const ServerProperty&)
{
connection.send_not_implemented();
}
};
}}}} // namespace rti::apputils::http::server
#endif // HTTP_SERVER_REQUEST_HANDLER_HPP
@@ -0,0 +1,125 @@
/*
* (c) Copyright, Real-Time Innovations, 2019.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef HTTP_SERVER_HPP
#define HTTP_SERVER_HPP
#include <memory>
#include <string>
#include <thread>
#include <CivetServer.h>
#include "ServerProperty.hpp"
#include "rti/apputils/http/server/detail/CivetWebHandler.hpp"
namespace rti { namespace apputils { namespace http { namespace server {
/**
* @brief Server defines an interface to CivetWeb::CivetServer and allow us to
* create handlers and close the server thought it.
*/
class Server {
public:
/**
* @brief Constructor of the Server class.
*
* @param options CivetServer options
* @param callbacks Callbacks methods
*/
Server(const ServerProperty& server_property,
const CivetCallbacks *callbacks = nullptr)
: server_(std::unique_ptr<CivetServer>(new CivetServer(
server_property.webserver_options(),
callbacks))),
server_property_(server_property),
handlers_()
{ }
~Server()
{
close();
mg_exit_library();
}
/**
* @brief This method closes the server, i.e. calls CivetServer::close().
*/
void close()
{
constexpr auto num_retries = 10u;
for (auto i = 0u; !can_close() && i < num_retries; ++i) {
std::this_thread::sleep_for(std::chrono::seconds(1));
}
server_->close();
}
/**
* @brief Checks if the server has any callbacks still running so that it
* can be closed.
*
* @return True if the server can be closed. False otherwise.
*/
bool can_close()
{
return std::none_of(
handlers_.begin(),
handlers_.end(),
[](const std::unique_ptr<CivetWebHandler>& handler) {
return handler->callbacks_running();
});
}
template <typename Handler>
void add_handler(Handler *handler)
{
handlers_.emplace_back(std::unique_ptr<CivetWebHandler>(
new CivetWebHandler(server_property_, *this, handler)));
server_->addHandler(Handler::URI_PATTERN, handlers_.back().get());
}
/**
* @brief Adds a URI handler. Existing handlers will be overwritten.
*
* @param uri String with the URI
* @param handler CivetServer pointer to the handler that to be added
*/
template <typename Handler>
void add_handler()
{
add_handler(new Handler());
}
/**
* @brief Adds a URI websocket handler. Existing websocket handlers will be
* overwritten.
*
* @param uri String with the URI
* @param websocket_handler CivetServer pointer to the handler that to be
* added
*/
void add_websocket_handler(const std::string& uri, void *websocket_handler)
{
server_->addWebSocketHandler(
uri,
reinterpret_cast<CivetWebSocketHandler *>(websocket_handler));
}
private:
std::unique_ptr<CivetServer> server_;
const ServerProperty& server_property_;
std::vector<std::unique_ptr<CivetWebHandler>> handlers_;
};
}}}} // namespace rti::apputils::http::server
#endif // HTTP_SERVER_HPP
@@ -0,0 +1,213 @@
/*
* (c) Copyright, Real-Time Innovations, 2022.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef HTTP_SERVER_PROPERTY_HPP
#define HTTP_SERVER_PROPERTY_HPP
#include <algorithm>
#include <dds/core/macros.hpp>
#include <rti/core/NativeValueType.hpp>
#include "rti/apputils/util/Path.hpp"
#include "rti/apputils/http/common/HttpUtils.hpp"
namespace rti { namespace apputils { namespace http { namespace server {
/**
* @brief This class contains the HTTP server properties
*/
class ServerProperty {
public:
/**
* @brief ServerProperty constructor
*/
ServerProperty(const std::string& ports = HTTP_SERVER_DEFAULT_PORT_STR)
: handle_options_request_(false)
{
listening_ports(ports);
}
/**
* @brief Destroy the Server Property object
*/
virtual ~ServerProperty() = default;
/**
* @brief Return the document root path.
*
* @return const std::string&
*/
const std::string& document_root() const
{
return document_root_;
}
/**
* @brief Return the web server options.
*
* @return const std::vector<std::string>&
*/
const std::vector<std::string>& webserver_options() const
{
return webserver_options_;
}
/**
* @brief Return the value of a web server options given it name. If the
* option is not found, the name will be returned.
*
* @return const std::string>& option value, or option name if not found.
*/
const std::string& webserver_option(std::string& name) const
{
auto it = std::find(
webserver_options_.begin(),
webserver_options_.end(),
name);
if (it != webserver_options_.end()
&& ++it != webserver_options_.end()) {
return *it;
}
return name;
}
/**
* @brief This method returns true if the server will handle OPTIONS
* requests, otherwise false.
*
* @return boolean
*/
bool handle_options_request() const
{
return handle_options_request_;
}
/**
* @brief Set the document root path.
*
* @param document_root
* @return ServerProperty&
*/
ServerProperty& document_root(const std::string& document_root)
{
if (!document_root.empty()) {
document_root_ = util::Path(document_root);
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
document_root_.exists(),
"Document root path does not exist");
set_webserver_option("document_root", document_root_.c_str());
}
return *this;
}
/**
* @brief Set the listening ports.
*
* @param listening_ports
* @return ServerProperty&
*/
ServerProperty& listening_ports(const std::string& listening_ports)
{
listening_ports_ = listening_ports;
set_webserver_option("listening_ports", listening_ports.c_str());
return *this;
}
/**
* @brief This method enters a key-value pair into the webserver_options
* array.
*
* @param key Name of the option to be set.
* @param value Value of the option.
*/
ServerProperty& set_webserver_option(const char *key, const char *value)
{
// Check if the option already exists.
auto it = std::find(
webserver_options_.begin(),
webserver_options_.end(),
key);
// if exists modify the value
if (it != webserver_options_.end()
&& ++it != webserver_options_.end()) {
*it = value;
} else {
// Add option name
webserver_options_.emplace_back(key);
// Add option value
webserver_options_.emplace_back(value);
}
return *this;
}
/**
* @brief This option enables the server to handle OPTIONS requests if true,
* otherwise civetweb will handle them.
*
* @param handle_options_request boolean value to handle OPTIONS requests.
* @return ServerProperty&
*/
ServerProperty& handle_options_request(bool handle_options_request)
{
handle_options_request_ = handle_options_request;
return *this;
}
static std::string parse_port(const std::string& host)
{
std::string::size_type scheme_pos = host.find("://");
if (scheme_pos == std::string::npos) {
RTI_THROW_SERVICE_EXCEPTION(
&RTI_LOG_INIT_FAILURE_s,
"Invalid scheme format");
}
std::string unformatted_host = host.substr(scheme_pos + 1);
std::string::size_type port_pos = unformatted_host.find(":");
if (port_pos == std::string::npos) {
RTI_THROW_SERVICE_EXCEPTION(
&RTI_LOG_INIT_FAILURE_s,
"Invalid host:port format");
}
std::string port_string = unformatted_host.substr(port_pos + 1);
try {
// Check that port has a valid format
std::stoi(port_string);
} catch (...) {
std::string error_msg;
error_msg.append("Invalid port value: ").append(port_string);
RTI_THROW_SERVICE_EXCEPTION(
&RTI_LOG_INIT_FAILURE_s,
error_msg.c_str());
}
return port_string;
}
private:
// Server options
util::Path document_root_;
std::string listening_ports_;
std::vector<std::string> webserver_options_;
bool handle_options_request_;
};
}}}} // namespace rti::apputils::http::server
#endif // HTTP_SERVER_PROPERTY_HPP
@@ -0,0 +1,320 @@
/*
* (c) Copyright, Real-Time Innovations, 2024.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef _WEBSOCKET_CONNECTION_H_
#define _WEBSOCKET_CONNECTION_H_
#include <civetweb.h>
#include <map>
#include <rti/apputils/StringBuffer.hpp>
#include <rti/apputils/http/common/WebSocketUtils.h>
#include <rti/apputils/http/common/StatusCode.hpp>
#include <rti/apputils/http/common/HttpException.hpp>
namespace rti { namespace apputils { namespace websocket {
/*
* This class wraps the state of the client connection. Every instance created
* from this class must be kept alive until the connection is closed. Whenever
* the server receives a new web_wait_factorysocket connection attempt, it
* creates a WebSocketConnection instance and this WebSocketConnection will be
* attached to the client connection.
*
* This class has the state of the client connection, the type of content that
* the client accepts and the bind ids that each connection have bound.
*/
class WebSocketConnection {
public:
explicit WebSocketConnection()
: state_(WebSocketStateKind::UNKNOWN),
content_type_(WebSocketContentType::UNKNOWN)
{ }
virtual ~WebSocketConnection() = default;
/**
* Overloading the == operator to compare connections.
* @param conn A pointer to the connection to compare
*/
virtual bool operator==(WebSocketConnection *conn) = 0;
/**
* This method returns the state of the connection
* @return WebSocketStateKind the current status
*/
WebSocketStateKind state() const
{
return state_;
}
/**
* This method returns the content type accepted for the connection
* @return WebSocketContentType
*/
WebSocketContentType content_type() const
{
return content_type_;
}
/**
* This method sets the state of the connection
* @param state WebSocketStateKind the new status
*/
void state(const WebSocketStateKind& state)
{
state_ = state;
}
/**
* This method sets the content type accepted for the connection
* @param content WebSocketContentType
*/
void content_type(const WebSocketContentType& content)
{
content_type_ = content;
}
/**
* This function sends a MG_WEBSOCKET_OPCODE_TEXT message to the user.
* @param msg The message string to be sent
*/
virtual void send(const StringBuffer& msg) = 0;
/**
* This function sends a MG_WEBSOCKET_OPCODE_PONG message to the user.
* @param msg The message string to be sent
*/
virtual void send_pong(const StringBuffer& msg) = 0;
/**
* This function creates the error message to the user.
* @param ex Excetion with the info of an issue
* @param save_ctx context with that point to the message
*/
void create_error(
RTIXMLSaveContext& save_ctx,
const rti::apputils::http::HttpException& ex)
{
struct DDS_PrintFormat format = DDS_PrintFormat_INITIALIZER;
DDS_PrintFormatProperty properties = DDS_PrintFormatProperty_INITIALIZER;
DDS_ReturnCode_t retcode = DDS_RETCODE_ERROR;
if (content_type_ == WebSocketContentType::JSON) {
properties.kind = DDS_JSON_PRINT_FORMAT;
} else if (content_type_ == WebSocketContentType::XML) {
properties.kind = DDS_XML_PRINT_FORMAT;
} else {
properties.kind = DDS_XML_PRINT_FORMAT;
}
retcode = DDS_PrintFormatProperty_to_print_format(&properties, &format);
if (retcode != DDS_RETCODE_OK) {
RTI_THROW_HTTP_EXCEPTION(
rti::apputils::http::StatusCode::internal_server_error,
&RTI_LOG_ANY_FAILURE_s,
"Unsupported content type");
}
format.print_top_level_beginning(&format, &save_ctx, "error", 0);
format.print_primitive_type_beginning(&format, &save_ctx, "code", 1);
RTIXMLSaveContext_freeform(
&save_ctx,
"%d",
ex.error_code());
format.print_primitive_type_ending(&format, &save_ctx, "code", 1);
RTIXMLSaveContext_freeform(&save_ctx, "%s", format.element_separator);
format.print_primitive_type_beginning(&format, &save_ctx, "message", 1);
RTIXMLSaveContext_freeform(
&save_ctx,
"%sError %s%s",
format.string_opening_delimiter,
ex.what(),
format.string_closing_delimiter);
format.print_primitive_type_ending(&format, &save_ctx, "message", 1);
RTIXMLSaveContext_freeform(&save_ctx, "%s", format.element_separator);
format.print_primitive_type_beginning(&format, &save_ctx, "method", 1);
RTIXMLSaveContext_freeform(
&save_ctx,
"%s%s%s",
format.string_opening_delimiter,
RTI_FUNCTION_NAME,
format.string_closing_delimiter);
format.print_primitive_type_ending(&format, &save_ctx, "method", 1);
format.print_top_level_ending(&format, &save_ctx, "error", 0);
}
/**
* This function sends an error message to the user.
* @param msg The error message that the error will have
*/
void send_error(const rti::apputils::http::HttpException& ex)
{
StringBuffer buffer;
RTIXMLSaveContext save_ctx = RTIXMLSaveContext_INITIALIZER;
save_ctx.sout = (char*) buffer.c_str();
if (!RTIOsapiUtility_sizeToUInt32(save_ctx.ssize, buffer.capacity())) {
RTI_THROW_HTTP_EXCEPTION(
rti::apputils::http::StatusCode::internal_server_error,
&RTI_LOG_ANY_FAILURE_s,
"Error allocating string");
}
create_error(save_ctx, ex);
if (save_ctx.outputStringLength + 1 > buffer.capacity()) {
buffer.ensure_capacity(save_ctx.outputStringLength + 1);
// Reset save_context
RTIXMLSaveContext_initialize(&save_ctx);
save_ctx.sout = (char *) buffer.c_str();
if (!RTIOsapiUtility_sizeToUInt32(save_ctx.ssize, buffer.capacity())) {
RTI_THROW_HTTP_EXCEPTION(
rti::apputils::http::StatusCode::internal_server_error,
&RTI_LOG_ANY_FAILURE_s,
"Error allocating string");
}
create_error(save_ctx, ex);
}
send(buffer);
}
/**
* This function creates the request error message to the user.
* @param ex RequestExcetion with the info of an issue
* @param save_ctx context with that point to the message
*/
void create_request_error(
RTIXMLSaveContext& save_ctx,
const RequestException& ex)
{
struct DDS_PrintFormat format = DDS_PrintFormat_INITIALIZER;
DDS_PrintFormatProperty properties = DDS_PrintFormatProperty_INITIALIZER;
DDS_ReturnCode_t retcode = DDS_RETCODE_ERROR;
if (content_type_ == WebSocketContentType::JSON) {
properties.kind = DDS_JSON_PRINT_FORMAT;
} else if (content_type_ == WebSocketContentType::XML) {
properties.kind = DDS_XML_PRINT_FORMAT;
} else {
properties.kind = DDS_XML_PRINT_FORMAT;
}
retcode = DDS_PrintFormatProperty_to_print_format(&properties, &format);
if (retcode != DDS_RETCODE_OK) {
RTI_THROW_HTTP_EXCEPTION(
rti::apputils::http::StatusCode::internal_server_error,
&RTI_LOG_ANY_FAILURE_s,
"Unsupported content type");
}
format.print_top_level_beginning(
&format,
&save_ctx,
WEBSOCKET_RESPONSE_NAME,
0);
format.print_primitive_type_beginning(&format, &save_ctx, "return_code", 1);
RTIXMLSaveContext_freeform(
&save_ctx,
"%d",
ex.error_code());
format.print_primitive_type_ending(&format, &save_ctx, "return_code", 1);
RTIXMLSaveContext_freeform(&save_ctx, "%s", format.element_separator);
format.print_primitive_type_beginning(&format, &save_ctx, "error", 1);
RTIXMLSaveContext_freeform(
&save_ctx,
"%s%s%s",
format.string_opening_delimiter,
ex.what(),
format.string_closing_delimiter);
format.print_primitive_type_ending(&format, &save_ctx, "error", 1);
RTIXMLSaveContext_freeform(&save_ctx, "%s", format.element_separator);
format.print_primitive_type_beginning(&format, &save_ctx, "method", 1);
RTIXMLSaveContext_freeform(
&save_ctx,
"%s%s%s",
format.string_opening_delimiter,
ex.get_method_name(),
format.string_closing_delimiter);
format.print_primitive_type_ending(&format, &save_ctx, "method", 1);
format.print_top_level_ending(
&format,
&save_ctx,
WEBSOCKET_RESPONSE_NAME,
0);
}
/**
* This function sends an REQUEST error message to the user.
* @param msg The error message that the error will have
*/
void send_request_error(const RequestException& ex)
{
StringBuffer buffer;
RTIXMLSaveContext save_ctx = RTIXMLSaveContext_INITIALIZER;
save_ctx.sout = (char*) buffer.c_str();
if (!RTIOsapiUtility_sizeToUInt32(save_ctx.ssize, buffer.capacity())) {
RTI_THROW_HTTP_EXCEPTION(
rti::apputils::http::StatusCode::internal_server_error,
&RTI_LOG_ANY_FAILURE_s,
"Error allocating string");
}
create_request_error(save_ctx, ex);
if (save_ctx.outputStringLength + 1 > buffer.capacity()) {
buffer.ensure_capacity(save_ctx.outputStringLength + 1);
// Reset save_context
RTIXMLSaveContext_initialize(&save_ctx);
save_ctx.sout = (char *) buffer.c_str();
if (!RTIOsapiUtility_sizeToUInt32(save_ctx.ssize, buffer.capacity())) {
RTI_THROW_HTTP_EXCEPTION(
rti::apputils::http::StatusCode::internal_server_error,
&RTI_LOG_ANY_FAILURE_s,
"Error allocating string");
}
create_request_error(save_ctx, ex);
}
send(buffer);
}
/**
* This function close a WebSocketConnection.
* @param exception with the error message that happend
*/
virtual void close() = 0;
private:
WebSocketStateKind state_;
WebSocketContentType content_type_;
};
}}} // namespace rti::webdds::websocket
#endif // _WEBSOCKET_CONNECTION_H_
@@ -0,0 +1,209 @@
/*
* (c) Copyright, Real-Time Innovations, 2015.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef CIVETWEB_HANDLER_HPP
#define CIVETWEB_HANDLER_HPP
#include <atomic>
#include <CivetServer.h>
#include <civetweb.h>
#include "rti/apputils/http/server/ServerProperty.hpp"
#include "rti/apputils/http/server/Connection.hpp"
#include "rti/apputils/http/server/RequestHandler.hpp"
namespace rti { namespace apputils { namespace http { namespace server {
// Forward declaration
class Server;
/**
* @brief WebHandler implements the handler functions of CivetWeb's API,
* wrapping the calls to the generic RequestHandler. The Request is specialized
* to an specific kind of request handler (e.g., PublisherHandler) whose type is
* specified via template.
*/
class CivetWebHandler : public CivetHandler {
public:
/**
* @brief Constructor of the CivetWebHandler class. It calls the constructor
* of the specific RequestHandler it implements (i.e., handler)
*
* @param web_server_property Reference to the ServerProperty that will
* be used by the RequestHandler.
* @param server Web's server.
*/
explicit CivetWebHandler(
const ServerProperty& web_server_property,
Server& server,
RequestHandler *handler)
: web_server_property_(web_server_property),
server_(server),
handler_(handler),
callbacks_counter_(0)
{ }
/**
* @brief Destructor of the CivetWebHandler class.
*/
virtual ~CivetWebHandler() = default;
/**
* @brief This method handles the POST operation.
*
* @param server CivetWeb's server.
* @param conn CiveWeb's connection.
* @return Always returns true given the fact that the operation is always
* processed by the underlying interfaces.
*/
virtual bool handlePost(CivetServer *, struct mg_connection *conn) final
{
CounterGuard guard { callbacks_counter_ };
Connection connection(conn);
handler_->handle_post(server_, connection, web_server_property_);
return true;
}
/**
* @brief This method handles the PUT operation.
*
* @param server CivetWeb's server.
* @param conn CiveWeb's connection.
* @return Always returns true given the fact that the operation is always
* processed by the underlying interfaces.
*/
virtual bool handlePut(CivetServer *, struct mg_connection *conn) final
{
CounterGuard guard { callbacks_counter_ };
Connection connection(conn);
handler_->handle_put(server_, connection, web_server_property_);
return true;
}
/**
* @brief This method handles the GET operation.
*
* @param server CivetWeb's server.
* @param conn CiveWeb's connection.
* @return Always returns true given the fact that the operation is always
* processed by the underlying interfaces.
*/
virtual bool handleGet(CivetServer *, struct mg_connection *conn) final
{
CounterGuard guard { callbacks_counter_ };
Connection connection(conn);
handler_->handle_get(server_, connection, web_server_property_);
return true;
}
/**
* @brief This method handles the DELETE operation.
*
* @param server CivetWeb's server.
* @param conn CiveWeb's connection.
* @return Always returns true given the fact that the operation is always
* processed by the underlying interfaces.
*/
virtual bool handleDelete(CivetServer *, struct mg_connection *conn) final
{
CounterGuard guard { callbacks_counter_ };
Connection connection(conn);
handler_->handle_delete(server_, connection, web_server_property_);
return true;
}
/**
* @brief This method handles the OPTIONS operation.
*
* @param server CivetWeb's server.
* @param conn CiveWeb's connection.
* @return true if the request was processed by the custom handler,
* otherwise false.
*/
virtual bool handleOptions(CivetServer *, struct mg_connection *conn) final
{
CounterGuard guard { callbacks_counter_ };
if (web_server_property_.handle_options_request()) {
Connection connection(conn);
handler_->handle_options(server_, connection, web_server_property_);
return true;
}
return false;
}
/**
* @brief This method handles the HEAD operation.
*
* @param server CivetWeb's server.
* @param conn CiveWeb's connection.
* @return Always returns true given the fact that the operation is always
* processed by the underlying interfaces.
*/
virtual bool handleHead(CivetServer *, struct mg_connection *conn) final
{
CounterGuard guard { callbacks_counter_ };
Connection connection(conn);
handler_->handle_head(server_, connection, web_server_property_);
return true;
}
/**
* @brief This method handles the PATCH operation.
*
* @param server CivetWeb's server.
* @param conn CiveWeb's connection.
* @return Always returns true given the fact that the operation is always
* processed by the underlying interfaces.
*/
virtual bool handlePatch(CivetServer *, struct mg_connection *conn) final
{
CounterGuard guard { callbacks_counter_ };
Connection connection(conn);
handler_->handle_patch(server_, connection, web_server_property_);
return true;
}
bool callbacks_running() const
{
return callbacks_counter_ != 0;
}
private:
struct CounterGuard {
CounterGuard(std::atomic_short& counter) : counter_(counter)
{
++counter_;
}
~CounterGuard()
{
--counter_;
}
std::atomic_short& counter_;
};
const ServerProperty& web_server_property_;
Server& server_;
std::unique_ptr<RequestHandler> handler_;
std::atomic_short callbacks_counter_;
};
}}}} // namespace rti::apputils::http::server
#endif // CIVETWEB_HANDLER_HPP
@@ -0,0 +1,165 @@
/*
* (c) Copyright, Real-Time Innovations, 2024.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef CIVETWEB_WEBSOCKET_HANDLER_H
#define CIVETWEB_WEBSOCKET_HANDLER_H
#include <CivetServer.h>
#include <civetweb.h>
#include <rti/apputils/http/server/CivetWebSocketConnection.hpp>
#include <rti/apputils/http/server/detail/WebSocketHandler.hpp>
namespace rti { namespace apputils { namespace websocket { namespace civetweb {
/**
* This class implements the CivetWebSocketHandler interface, which is part
* of the CivetWeb API. This class has a WebSocketHandler. It allows us
* to isolate the WebSocket handling that CivetWeb does and the WebSocket
* handling that our WebSocketAPI implements
*/
class CivetWebSocketHandlerImpl : public CivetWebSocketHandler {
public:
CivetWebSocketHandlerImpl(WebSocketHandler& handler) : handler_(handler)
{ }
/**
* This method handles new client connections to the server. For such
* purpose, it creates a new `WebSocketConnection` and attaches it to the
* internal CivetWeb `mg_connection`. This mechanisms allows existing
* connections to be reused in the future.
* @param server CivetWeb server. Unused parameter.
* @param conn CivetWeb connection.
* @return Boolean value indicating the success or failure of the operation.
*/
bool handleConnection(CivetServer *, const struct mg_connection *conn)
{
WebSocketConnection *client_conn =
new CivetWebSocketConnection(const_cast<mg_connection *>(conn));
handler_.handle_connection(*client_conn);
mg_set_user_connection_data(const_cast<mg_connection *>(conn), client_conn);
/*
* client_conn is not being leaked here, it's stored indirectly
* in the user connection data that can be get by calling
* mg_get_user_connection_data as we can see on handleClose.
*/
/* coverity[leaked_storage : FALSE] */
return true;
}
/**
* This method is called when a client connection has ended the handshake
* protocol with success.
* @param server CivetWeb server. Unused parameter.
* @param conn CivetWeb connection
*/
void handleReadyState(CivetServer *, struct mg_connection *conn)
{
WebSocketConnection *client = static_cast<WebSocketConnection *>(
mg_get_user_connection_data(conn));
handler_.handle_ready_state(*client);
}
/**
* This method is called every time a client sends a message to the server
* through the WebSocket connection.
* @param server CivetWeb server. Unused parameter
* @param conn CivetWeb connection
* @param bits A byte with the kind of data (TEXT, PONG, ...)
* @param data String containing the received message, it is not
* null-terminated
* @param data_size String size, the size of the string without the null
* character.
*/
bool handleData(
CivetServer *,
struct mg_connection *conn,
int bits,
char *data,
size_t data_len)
{
StringBuffer msg;
WebSocketConnection *client = static_cast<WebSocketConnection *>(
mg_get_user_connection_data(conn));
// The last byte of the bits say what kind of message it is
switch (((unsigned char) bits) & 0x0F) {
case MG_WEBSOCKET_OPCODE_TEXT:
// This marks the end of the string
*(data + data_len) = '\0';
msg = StringBuffer(data);
try {
handler_.handle_data(*client, msg);
} catch (const RequestException& ex) {
DDSLog_exception(&RTI_LOG_ANY_FAILURE_s, ex.what());
client->send_request_error(ex);
} catch (const rti::apputils::http::HttpException& ex) {
DDSLog_exception(&RTI_LOG_ANY_FAILURE_s, ex.what());
client->send_error(ex);
} catch (...) {
std::string message = "Unexpected exception. The connection will be closed.";
DDSLog_exception(&RTI_LOG_ANY_FAILURE_s, message.c_str());
message.append(": ");
message.append(RTI_FUNCTION_NAME);
client->send(message);
// Close the connection (Especially to avoid vulnerabilities)
client->state(WebSocketStateKind::CLOSE);
client->close();
}
break;
case MG_WEBSOCKET_OPCODE_PING:
// This marks the end of the string
data[data_len] = '\0';
msg = StringBuffer(data);
// just send back the message data in a pong frame
client->send_pong(msg);
break;
default:
break;
}
return true;
}
/**
* This method is called when a client closes the connection. The method
* deletes the connection object.
* @param server CivetWeb Server
* @param conn CivetWeb connection
*/
void handleClose(CivetServer *, const struct mg_connection *conn)
{
struct mg_context *ctx = mg_get_context(conn);
WebSocketConnection *client =
static_cast<WebSocketConnection *>(mg_get_user_connection_data(conn));
if (client->state() != WebSocketStateKind::CLOSE){
handler_.handle_close(*client);
}
mg_lock_context(ctx);
delete client;
mg_set_user_connection_data(const_cast<mg_connection *>(conn), NULL);
mg_unlock_context(ctx);
}
~CivetWebSocketHandlerImpl()
{
}
private:
WebSocketHandler& handler_;
};
}}}} // namespace rti::webdds::websocket::civetweb
#endif
@@ -0,0 +1,63 @@
/*
* (c) Copyright, Real-Time Innovations, 2024.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef WEBSOCKET_HANDLER_HPP
#define WEBSOCKET_HANDLER_HPP
#include <rti/apputils/http/server/WebSocketConnection.hpp>
#include <rti/apputils/StringBuffer.hpp>
namespace rti { namespace apputils { namespace websocket {
/**
* This class is a generic handler for WebSocket communication. It
* implements methods that handle all the different events that can be
* triggered in the context of a WebSocket connection.
*/
class WebSocketHandler {
public:
/**
* This method is called when a client establishes a connection with the
* server. When that happens, we set the status of the WebSocketConnection
* to WebSocketStatus_CREATED.
* @param conn WebSocketConnection connection that will be created
*/
virtual void handle_connection(WebSocketConnection& conn) = 0;
/**
* This method is called when the websocket's handshake protocol has
* ended successfully. The state of the connection is marked as
* WebSocketStateKind::OPEN.
* @param conn WebSocketConnection connection that will be updated
*/
virtual void handle_ready_state(WebSocketConnection& conn) = 0;
/**
* This method is called every time a client sends a message. The message is
* wrapped in a StringBuffer. Upon reception, the method identifies the
* message kind and calls the appropriate Handler for that kind of message.
* @param conn WebSocketConnection connection that sent the message
* @param msg StringBuffer with the corresponding message
*/
virtual void handle_data(
WebSocketConnection& conn,
const StringBuffer& msg) = 0;
/**
* This method is called when a client closes a connection with the server.
* It detaches all conditions that have been previously attached to the
* connection.
* @param conn WebSocketConnection connection that will be closed
*/
virtual void handle_close(WebSocketConnection& conn) = 0;
};
}}} // namespace rti::apputils::websocket
#endif // WEBSOCKET_HANDLER_HPP
@@ -0,0 +1,304 @@
/*
(c) Copyright, Real-Time Innovations, 2017-
All rights reserved.
No duplications, whole or partial, manual or electronic, may be made without
express written permission. Any such copies, or revisions thereof, must
display this notice unaltered.
This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef HPP_SERVICE_LOG_CONFIG_HPP_
#define HPP_SERVICE_LOG_CONFIG_HPP_
// IMPORTANT: macros.hpp must be the first RTI header included in every header
// file so that symbols are exported correctly on Windows
#include <dds/core/macros.hpp>
#include <cstdio>
#include <cstdarg>
#include "log/log_common.h"
#include <rti/advlog/Context.hpp>
#include <rti/config/Logger.hpp>
#include <rti/apputils/ServiceException.hpp>
#define RTI_SUBMODULE_MASK_ALL (0xFFFF)
namespace rti { namespace apputils { namespace log {
/**
* @deprecated This class is deprecated and may be removed in future releases.
*
* @brief This class is used to configure the logging verbosity and mask of a
* Logger instance.
*/
class LogConfig {
public:
static LogConfig& instance()
{
static LogConfig singleton;
return singleton;
}
RTILogBitmap instrumentation_mask() const
{
return instrumentation_mask_;
}
LogConfig& instrumentation_mask(RTILogBitmap logmask)
{
instrumentation_mask_ = logmask;
return *this;
}
rti::config::Verbosity verbosity()
{
return static_cast<rti::config::Verbosity::type>(instrumentation_mask_);
}
void verbosity(rti::config::Verbosity verbosity)
{
instrumentation_mask(
static_cast<RTILogBitmap>(verbosity.underlying()));
}
~LogConfig()
{
}
static RTILogBitmap mask_all()
{
return RTI_SUBMODULE_MASK_ALL;
}
static RTILogBitmap mask_none()
{
return 0;
}
protected:
// Constructor is private: this class is a singleton
LogConfig()
: instrumentation_mask_(RTI_LOG_BITMAP_DEFAULT)
{
RTILog_appendPrintMask(RTI_LOG_PRINT_BIT_AT_METHOD);
}
RTILogBitmap instrumentation_mask_;
};
inline
rti::config::Verbosity dds_verbosity_from_level(int32_t level)
{
switch (level) {
case 6:
return rti::config::Verbosity::STATUS_REMOTE;
case 5:
/* no break */
case 4:
return rti::config::Verbosity::WARNING;
case 3:
/* no break */
case 2:
/* no break */
case 1:
return rti::config::Verbosity::EXCEPTION;
case 0:
return rti::config::Verbosity::SILENT;
default:
return rti::config::Verbosity::EXCEPTION;
}
}
inline
rti::config::Verbosity service_verbosity_from_level(int32_t level)
{
RTILogBitmap native_verbosity = RTI_LOG_BITMAP_NONE;
switch (level) {
/* no break */
case 6:
native_verbosity |= RTI_LOG_BIT_CONTENT;
/* no break */
case 5:
native_verbosity |= RTI_LOG_BIT_PERIODIC;
/* no break */
case 4:
native_verbosity |= RTI_LOG_BIT_REMOTE;
/* no break */
case 3:
native_verbosity |= RTI_LOG_BIT_LOCAL;
/* no break */
case 2:
native_verbosity |= RTI_LOG_BIT_WARN;
/* no break */
case 1:
native_verbosity |= RTI_LOG_BIT_EXCEPTION | RTI_LOG_BIT_FATAL_ERROR;
break;
case 0:
break;
default:
native_verbosity = RTI_LOG_BITMAP_DEFAULT;
break;
}
return static_cast<rti::config::Verbosity::type>(native_verbosity);
}
/*
* Example:
* MyCatcherMethod: caught exception from:
* MyThrowerMethod:!init failure
*/
inline void log_service_exception(
RTILogBitmap instrumentation_mask,
const rti::apputils::ServiceException& exception,
const char *method_name)
{
RTILogMessage log_message;
log_message.format = "caught exception from:\n %s";
log_message.messageId = exception.message_id();
if (instrumentation_mask & RTI_LOG_BIT_EXCEPTION) {
RTILogMessage_printWithParams(
RTI_LOG_PRINT_FORMAT_MASK_ALL_EXCEPT_LOCATION_FILELINE_AND_MODULE,
RTI_LOG_BIT_EXCEPTION,
MODULE_LOG,
NULL, /* filename */
0, /* lineno */
method_name,
&log_message,
exception.what());
}
}
} } } /* namespace rti::apputils::log */
/* ================================================================= */
/* Logging macros */
/* ================================================================= */
/* ----------------------------------------------------------------- */
/*i
* @brief See \ref RTILogCategory_log
*/
#define SERVICELogger_log(instrumentationBit__, logMessage__, worker__, ...) \
RTILogCategory_log( \
(MODULE_LOG_CONFIG).instrumentation_mask(), \
RTI_SUBMODULE_MASK_ALL, \
RTI_SUBMODULE_MASK_ALL, \
(instrumentationBit__), \
DEFINE_MODULE_NUMBER(MODULE_NS), \
(logMessage__), \
(worker__), \
__VA_ARGS__)
/*
* --- C-like logger macros ---------------------------------------------------
*/
#define SERVICELog_fromException(SERVICE_EXCEPTION) \
rti::apputils::log::log_service_exception( \
(MODULE_LOG_CONFIG).instrumentation_mask(), \
SERVICE_EXCEPTION, \
RTI_FUNCTION_NAME)
/*
* For legacy reasons, we keep the definition of freeForm.
* freeForm is now deprecated and it should not be used anymore.
* These are the alternatives to freeForm (only to be used in test code or
* non-production code, like an instrumented build):
* - If you need to log (verbosity filtered) an unformatted parameterized string
* you can use RTITestLog_logParamString (in tests) or
* RTILogParamString_logUnformatted (in non-test, instrumented build code).
* This is equivalent to old freeForm().
* - If you just need to print (not verbosity filtered) an unformatted parameterized
* string, use RTILogParamString_printPlain.
* - Finally, if you need print a formatted parameterized string, use RTILogParamString_print.
*/
#define SERVICELog_freeForm(INSTRUMENT_BIT, ...) \
RTILogParamString_logUnformatted( \
(MODULE_LOG_CONFIG).instrumentation_mask(), \
RTI_SUBMODULE_MASK_ALL, \
RTI_SUBMODULE_MASK_ALL, \
(INSTRUMENT_BIT), \
__VA_ARGS__)
#define SERVICELog_logMessage(INSTRUMENT_BIT, ...) \
RTILogMessage_log( \
(MODULE_LOG_CONFIG).instrumentation_mask(), \
RTI_SUBMODULE_MASK_ALL, \
RTI_SUBMODULE_MASK_ALL, \
RTI_LOG_PRINT_FORMAT_MASK_ALL_EXCEPT_LOCATION, \
(INSTRUMENT_BIT), \
DEFINE_MODULE_NUMBER(MODULE_NS), \
__VA_ARGS__)
#define SERVICELogMessage_log(INSTRUMENT_BIT, ...) \
RTILogMessage_log( \
(MODULE_LOG_CONFIG).instrumentation_mask(), \
RTI_SUBMODULE_MASK_ALL, \
RTI_SUBMODULE_MASK_ALL, \
RTI_LOG_PRINT_FORMAT_MASK_ALL, \
(INSTRUMENT_BIT), \
DEFINE_MODULE_NUMBER(MODULE_NS), \
__VA_ARGS__)
#define SERVICELog_exception(...) \
SERVICELogMessage_log(RTI_LOG_BIT_EXCEPTION, __VA_ARGS__)
#define SERVICELog_warn(...) \
SERVICELogMessage_log(RTI_LOG_BIT_WARN, __VA_ARGS__)
#define SERVICELog_local(...) \
SERVICELogMessage_log(RTI_LOG_BIT_LOCAL, __VA_ARGS__)
#define SERVICELog_content(...) \
SERVICELogMessage_log(RTI_LOG_BIT_CONTENT, __VA_ARGS__)
#define SERVICELog_remote(...) \
SERVICELogMessage_log(RTI_LOG_BIT_REMOTE, __VA_ARGS__)
#define SERVICELog_any(INSTRUMENT_BIT, ...) \
if ((((MODULE_LOG_CONFIG).instrumentation_mask()) & (INSTRUMENT_BIT)) \
&& ((RTI_SUBMODULE_MASK_ALL) & (RTI_SUBMODULE_MASK_ALL)))
#define SERVICELog_flushActivityContextWithAction(INSTRUMENT_BIT, ACTION) \
SERVICELog_logMessage((INSTRUMENT_BIT), &RTI_LOG_ANY_s, (ACTION));
#define SERVICELog_flushActivityContext(INSTRUMENT_BIT) \
SERVICELog_logMessage((INSTRUMENT_BIT), &RTI_LOG_ANY_s, "");
/*
* The macro:
* - Creates a contextScope
* - Enter a resource entry into the context.
* - Enter an activity entry into the context.
*
* When the function where the macro is called is end, the destructor of
* the contextScope will be called and the entries will leave the context.
*/
#define RTI_SERVICE_CONTEXT_ENTER( \
RESOURCE_ID, \
...) \
rti::advlog::ContextScope context_scope_guard_implicit__; \
context_scope_guard_implicit__.enter(RESOURCE_ID); \
context_scope_guard_implicit__.enter(__VA_ARGS__)
#define RTI_SERVICE_CONTEXT_ENTER_LOG( \
RESOURCE_ID, \
...) \
RTI_SERVICE_CONTEXT_ENTER( \
RESOURCE_ID, \
__VA_ARGS__); \
SERVICELog_flushActivityContext(RTI_LOG_BIT_LOCAL)
#endif /* HPP_SERVICE_LOG_CONFIG_HPP_ */
@@ -0,0 +1,37 @@
/*
* $Id$
*
* (c) Copyright, Real-Time Innovations, 2016-.
* All rights reserved.
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef HPP_SERVICE_MONITORING_COUNTERS_HPP_
#define HPP_SERVICE_MONITORING_COUNTERS_HPP_
// IMPORTANT: macros.hpp must be the first RTI header included in every header
// file so that symbols are exported correctly on Windows
#include <dds/core/macros.hpp>
#include <dds/core/types.hpp>
namespace rti { namespace apputils { namespace monitoring {
struct NetworkPerformanceCounters {
NetworkPerformanceCounters()
:received_samples(0), received_bytes(0), latency_ms(0)
{
}
int64_t received_samples;
int64_t received_bytes;
int64_t latency_ms;
};
} } }// rti::apputils::monitoring
#endif /* HPP_SERVICE_MONITORING_COUNTERS_HPP_ */
@@ -0,0 +1,52 @@
/*
* $Id$
*
* (c) Copyright, Real-Time Innovations, 2016-.
* All rights reserved.
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef HPP_SERVICE_MONITORING_UTILS_HPP_
#define HPP_SERVICE_MONITORING_UTILS_HPP_
// IMPORTANT: macros.hpp must be the first RTI header included in every header
// file so that symbols are exported correctly on Windows
#include <dds/core/macros.hpp>
#include "osapi/osapi_host.h"
#include "osapi/osapi_process.h"
#include "apputils/apputils_monitoring.h"
#include <rti/core/Guid.hpp>
#include <rti/apputils/ResourceIdentifier.hpp>
namespace rti { namespace apputils { namespace monitoring {
inline
void compute_object_id(
rti::core::Guid& out_guid,
const rti::core::Guid& application_id,
const rti::apputils::ResourceIdentifier& resource_id)
{
APPUTILSMonitoringSupport_compute_resource_guid(
&out_guid.native(),
&application_id.native(),
&resource_id.native());
}
inline
void compute_application_guid(
rti::core::Guid &out_guid,
const std::string& application_name)
{
APPUTILSMonitoringSupport_compute_application_guid(
&out_guid.native(),
application_name.c_str());
}
} } }// rti::apputils::monitoring
#endif /* HPP_SERVICE_MONITORING_UTILS_HPP_ */
@@ -0,0 +1,214 @@
/*
(c) Copyright, Real-Time Innovations, 2023-.
All rights reserved.
No duplications, whole or partial, manual or electronic, may be made
without express written permission. Any such copies, or
revisions thereof, must display this notice unaltered.
This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef HPP_APPUTILS_SQLITEVERIFIER_HPP_
#define HPP_APPUTILS_SQLITEVERIFIER_HPP_
#include "rti/apputils/SQLiteConnection.hpp"
#include "rti/apputils/test/TestFramework.hpp"
#include "rti/util/util.hpp"
#include "rti/recording/storage/EStorageFormat.hpp"
namespace rti { namespace apputils { namespace test {
/**
* @brief Basic utility class with different checks for an SQLite Connection.
*/
class SQLiteVerifierBase {
public:
SQLiteVerifierBase(rti::apputils::SQLiteConnection& connection) :
connection_(connection) { }
bool check_table_exists(const std::string& table)
{
std::ostringstream string_stream;
string_stream << "SELECT name FROM sqlite_master WHERE name = '";
string_stream << table;
string_stream << "'";
rti::apputils::SQLiteConnection::RowIterator row_it =
connection_.begin(string_stream.str());
if (!row_it.at_end()) {
return true;
}
return false;
}
bool wait_till_table_exists(
const std::string& table,
const unsigned int seconds = DEFAULT_TEST_TIMEOUT_SEC)
{
bool exists = check_table_exists(table);
dds::core::Duration sleepDuration(1,0);
for (uint32_t i = 0; i < seconds && !exists; i++) {
rti::util::sleep(sleepDuration);
exists = check_table_exists(table);
}
if (!exists) {
RTITestLogger_log(
RTI_LOG_BIT_EXCEPTION,
&RTI_TEST_LOG_FAILURE_TEMPLATE,
"Table not found: %s",
table.c_str());
}
return exists;
}
bool check_column_exists(
const std::string& table,
const std::string& column)
{
std::ostringstream string_stream;
string_stream << "PRAGMA table_info([" << table << "])";
rti::apputils::SQLiteConnection::RowIterator row_it =
connection_.begin(string_stream.str());
while (!row_it.at_end()) {
std::string column_name;
row_it.get(1, column_name);
if (column_name.compare(column) == 0) {
return true;
}
++row_it;
}
return false;
}
uint32_t num_rows_for_statement(const std::string& sql)
{
rti::apputils::SQLiteConnection::RowIterator row_it =
connection_.begin(sql);
uint32_t row_count = 0;
while (!row_it.at_end()) {
row_count++;
++row_it;
}
return row_count;
}
/**
* @pre check_table_exists(table) == true
*/
uint32_t num_rows(
const std::string& table,
const bool filter_invalid = false)
{
std::ostringstream string_stream;
string_stream << "SELECT COUNT(*) FROM [" << table << "]";
if (filter_invalid) {
string_stream << " WHERE SampleInfo_valid_data = 1";
}
rti::apputils::SQLiteConnection::RowIterator row_it =
connection_.begin(string_stream.str());
int64_t value = 0;
row_it.get(0, value);
return (uint32_t) value;
}
bool check_num_rows(
const std::string& table,
const uint32_t expected_count,
const bool filter_invalid = false)
{
try {
RTI_TEST_ASSERT_EQUALS_INT(
expected_count,
num_rows(table, filter_invalid));
} catch (const std::exception&) {
return false;
}
return true;
}
bool check_num_rows_for_statement(
const std::string& sql,
const unsigned int expected_count)
{
try {
RTI_TEST_ASSERT_EQUALS_INT(
expected_count,
num_rows_for_statement(sql));
} catch (const std::exception&) {
return false;
}
return true;
}
bool wait_till_num_rows(
const std::string& table,
const unsigned int expected_count,
const bool filter_invalid = false,
const unsigned int seconds = DEFAULT_TEST_TIMEOUT_SEC)
{
try {
RTI_TEST_WAIT_FOR_EQUALS_INT(
expected_count,
num_rows(table, filter_invalid),
static_cast<int32_t>(seconds));
} catch (const std::exception& ex) {
RTITestLogger_log(
RTI_LOG_BIT_EXCEPTION,
&RTI_TEST_LOG_FAILURE_TEMPLATE,
"exception thrown: %s",
ex.what());
return false;
}
return true;
}
bool wait_till_num_rows_for_statement(
const std::string& sql,
const unsigned int expected_count,
const unsigned int seconds = DEFAULT_TEST_TIMEOUT_SEC)
{
try {
RTI_TEST_WAIT_FOR_EQUALS_INT(
expected_count,
num_rows_for_statement(sql),
static_cast<int32_t>(seconds));
} catch (const std::exception& ex) {
RTITestLogger_log(
RTI_LOG_BIT_EXCEPTION,
&RTI_TEST_LOG_FAILURE_TEMPLATE,
"exception thrown: %s",
ex.what());
return false;
}
return true;
}
bool check_num_rows_invariant(
const std::string& table,
const unsigned int expected_count,
const bool filter_invalid = false,
const unsigned int seconds = DEFAULT_TEST_TIMEOUT_SEC)
{
const struct RTINtpTime sleepTime = {0,429496730};
int sleeps = 0;
bool result = check_num_rows(table, expected_count, filter_invalid);
while(result && sleeps < (static_cast<int32_t>(seconds)) * 10) {
RTIOsapiThread_sleep(&sleepTime);
++sleeps;
result = check_num_rows(table, expected_count, filter_invalid);
}
return result;
}
protected:
rti::apputils::SQLiteConnection& connection_;
};
} } } /* namespace rti::apputils::test */
#endif /* HPP_APPUTILS_SQLITEVERIFIER_HPP_ */
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,95 @@
/*
(c) Copyright, Real-Time Innovations, 2023.
All rights reserved.
No duplications, whole or partial, manual or electronic, may be made
without express written permission. Any such copies, or
revisions thereof, must display this notice unaltered.
This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef HPP_APPUTILS_TEST_LOG_CAPTURE_HPP_
#define HPP_APPUTILS_TEST_LOG_CAPTURE_HPP_
// IMPORTANT: macros.hpp must be the first RTI header included in every header
// file so that symbols are exported correctly on Windows
#include <dds/core/macros.hpp>
#include <rti/apputils/ServiceException.hpp>
#include "rtitest/test_setting.h"
#include "ddsctesthelpers/test_helper.h"
namespace rti { namespace apputils { namespace test {
/**
* @brief This class is a C++ wrapper for the APPUTILSTestUtils_LoggerDevice
* type, used to capture logs and find expected messages.
*/
class TestLogCapture {
public:
TestLogCapture(const std::string& expected_message) :
expected_message_(expected_message)
{
device_data_ = DDSCTesterHelperLoggerDeviceData_INITIALIZER(NULL);
if (!DDSCTesterHelperLoggerDeviceData_initialize(&device_data_)) {
RTI_THROW_SERVICE_EXCEPTION(
&RTI_LOG_INIT_FAILURE_s,
"logger device data");
}
device_data_.expectedMessage = expected_message_.c_str();
if (!DDSCTesterHelper_cacheLoggingConfiguration()) {
RTI_THROW_SERVICE_EXCEPTION(
&RTI_LOG_ANY_FAILURE_s,
"cache logging configuration");
}
// Prepare the device logger for catching the expected message
logger_device_ = NDDS_Config_LoggerDevice_INITIALIZER;
logger_device_.device_data = &device_data_;
logger_device_.write = DDSCTesterHelper_loggerDeviceWrite;
if (!NDDS_Config_Logger_set_output_device(
NDDS_Config_Logger_get_instance(),
&logger_device_)) {
RTI_THROW_SERVICE_EXCEPTION(
&RTI_LOG_INIT_FAILURE_s,
"set output device");
}
}
~TestLogCapture()
{
DDSCTesterHelperLoggerDeviceData_finalize(&device_data_);
// Restore the logging configuration; this will restore the previous
// output device if it was changed
if (!DDSCTesterHelper_restoreLoggingConfiguration()) {
RTITestLogger_log(
RTI_LOG_BIT_EXCEPTION,
&RTI_TEST_LOG_FAILURE_TEMPLATE,
"restore logging configuration");
}
}
unsigned int found_messages() const
{
return device_data_.foundMessages;
}
private:
struct NDDS_Config_LoggerDevice logger_device_;
struct DDSCTesterHelperLoggerDeviceData device_data_;
std::string expected_message_;
};
} } } /* namespace rti::apputils::test */
#endif /* HPP_APPUTILS_TEST_LOG_CAPTURE_HPP_ */
// --------------------------------------------------------------------
/* End $Id$ */
@@ -0,0 +1,83 @@
/*
(c) Copyright, Real-Time Innovations, 2018.
All rights reserved.
No duplications, whole or partial, manual or electronic, may be made
without express written permission. Any such copies, or
revisions thereof, must display this notice unaltered.
This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef HPP_APPUTILS_LOCKEDBUFFER_HPP_
#define HPP_APPUTILS_LOCKEDBUFFER_HPP_
// IMPORTANT: macros.hpp must be the first RTI header included in every header
// file so that symbols are exported correctly on Windows
#include <dds/core/macros.hpp>
#include "dds/core/types.hpp"
namespace rti { namespace apputils { namespace util {
/**
* A type-agnostic class that services as a locked, unmodifiable region in
* memory. This is handy to transport a data buffer from one code region to
* another without having to perform any copy operations. Producer of the buffer
* and consumer of the buffer know how to work with the raw data.
*/
class UntypedLockedBuffer {
public:
UntypedLockedBuffer() : buffer_(NULL), size_(0) {}
UntypedLockedBuffer(const void *buffer, const uint32_t size) :
buffer_(buffer), size_(size) {}
~UntypedLockedBuffer() {}
void set(const void *buffer, const uint32_t size)
{
buffer_ = buffer;
size_ = size;
}
void reset()
{
buffer_ = NULL;
size_ = 0;
}
uint32_t size() const { return size_; }
protected:
const void * buffer() const { return buffer_; }
private:
const void *buffer_;
uint32_t size_;
};
/**
* This class represents a locked, unmodifiable region in memory. This is handy
* to transport a data buffer from one code region to another without having to
* perform any copy operations. Producer of the buffer and consumer of the
* buffer know how to work with the raw data.
*/
template <typename T>
class LockedBuffer : public UntypedLockedBuffer {
public:
LockedBuffer() : UntypedLockedBuffer() {}
LockedBuffer(const T *buffer, const uint32_t size) :
UntypedLockedBuffer(static_cast<const void *>(buffer), size) {}
~LockedBuffer() {}
void set(const T *buffer, const uint32_t size)
{
UntypedLockedBuffer::set(static_cast<const void *>(buffer), size);
}
const T * buffer() const {
return static_cast<const T *>(UntypedLockedBuffer::buffer());
}
};
} } } // namespace rti::apputils::util
#endif /* HPP_APPUTILS_LOCKEDBUFFER_HPP_ */
@@ -0,0 +1,495 @@
/*
* (c) Copyright, Real-Time Innovations, 2018.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef HPP_APPUTILS_PATH_HPP_
#define HPP_APPUTILS_PATH_HPP_
// IMPORTANT: macros.hpp must be the first RTI header included in every header
// file so that symbols are exported correctly on Windows
#include <dds/core/macros.hpp>
#include <string>
#include <vector>
#include <cstdio>
#include <iostream>
#include <fstream>
#include <sstream>
#include <stdint.h>
#include <cerrno>
#include <sys/types.h>
#include <sys/stat.h>
#if defined(RTI_UNIX) || defined(RTI_QNX)
#include <unistd.h>
#include <time.h>
#endif
#if defined(RTI_UNIX) || defined(RTI_QNX)
#if !defined(_XOPEN_SOURCE) && !defined(RTI_DARWIN)
#define _XOPEN_SOURCE 600
#endif
#include <ftw.h>
#include <unistd.h>
#include <sys/stat.h>
#include <stdint.h>
#endif
#ifdef RTI_WIN32
#include <windows.h>
#endif
#include "osapi/osapi_type.h"
#include "osapi/osapi_utility.h"
#include <rti/core/Exception.hpp>
#include <rti/apputils/ServiceException.hpp>
namespace rti { namespace apputils { namespace util {
class Path {
public:
Path()
{ }
Path(const std::string& path) : path_string_(path)
{
path_string_ = remove_double_separators(path_string_);
path_string_ = remove_trailing_separators(path_string_);
}
Path(const Path& path) : path_string_(path.path_string_)
{ }
operator const std::string &() const
{
return path_string_;
}
operator const char *() const
{
return path_string_.c_str();
}
const char *c_str() const
{
return path_string_.c_str();
}
bool empty() const
{
return path_string_.empty();
}
void append(const std::string& fragment)
{
if (!path_string_.empty()) {
path_string_ += "/";
}
path_string_ += fragment;
path_string_ = remove_double_separators(path_string_);
path_string_ = remove_trailing_separators(path_string_);
}
void append(const Path& fragment)
{
append((const std::string&) fragment);
}
void operator+=(const std::string& fragment)
{
append(fragment);
}
void operator+=(const Path& fragment)
{
append((const std::string&) fragment);
}
Path operator+(const std::string& other) const
{
Path path(*this);
path.append(other);
return path;
}
Path operator+(const Path& other) const
{
Path path(*this);
path.append(other);
return path;
}
int compare(const Path& other)
{
return path_string_.compare((const std::string&) other);
}
int compare(const std::string& other)
{
return path_string_.compare(other);
}
int compare(const char *other)
{
return path_string_.compare(other);
}
/**
* Path is interpreted as a file. Return whether the file exists or not.
*/
bool exists() const
{
// Check if the path is a file and if it exists
if (RTIOsapiFile_exists(path_string_.c_str()) == RTI_TRUE) {
return true;
} else {
#if defined(RTI_UNIX) || defined(RTI_QNX)
// Otherwise check if it's a directory and if it exists
struct stat info;
if (stat(path_string_.c_str(), &info) == 0
&& S_ISDIR(info.st_mode)) {
return true;
} else {
return false;
}
#elif defined(RTI_WIN32)
#ifndef S_ISDIR
#define S_ISDIR(mode) (((mode) &_S_IFMT) == _S_IFDIR)
#endif
// Otherwise check if it's a directory and if it exists
struct _stat info;
if (_stat(path_string_.c_str(), &info) == 0
&& S_ISDIR(info.st_mode)) {
return true;
} else {
return false;
}
#endif
}
}
static void create_directory(const Path& path)
{
#if defined(RTI_UNIX) || defined(RTI_QNX)
errno = 0;
if (mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0
&& (errno != EEXIST)) {
std::stringstream error_msg;
error_msg << "!create directory: " << path << "\n"
<< "CreateDirectory() error code: " << errno << "\n";
throw dds::core::IllegalOperationError(error_msg.str());
}
#elif defined(RTI_WIN32)
if (!CreateDirectory(path, NULL)) {
DWORD last_error = GetLastError();
if (last_error != ERROR_ALREADY_EXISTS) {
std::stringstream error_msg;
error_msg << "!create directory: " << path << "\n"
<< "CreateDirectory() error code: " << last_error
<< "\n";
throw dds::core::IllegalOperationError(error_msg.str());
}
}
#else
throw dds::core::IllegalOperationError("unsupported");
#endif
}
/**
* Interpreting the path as a directory, create any non-existing directories
* in the path.
*/
void create_directory()
{
// If the path is a file, and it exists, exit
if (exists()) {
return;
}
// Get splits, build a
std::vector<std::string> splits = split();
Path aux;
for (std::vector<std::string>::iterator split_it = splits.begin();
split_it != splits.end();
split_it++) {
aux += *split_it;
if (!aux.exists()) {
create_directory(aux);
}
}
}
static void remove_directory(const Path& path)
{
#if defined(RTI_UNIX) || defined(RTI_QNX)
errno = 0;
if (nftw(path, remove_file, 20, FTW_DEPTH) == -1) {
if (errno != ENOENT) {
perror("nftw");
RTI_THROW_SERVICE_EXCEPTION(
&RTI_LOG_ANY_FAILURE_s,
"remove_directory() error");
}
}
#else
if (!RTIOsapiFile_removeFileOrDirectory(path.c_str())) {
RTI_THROW_SERVICE_EXCEPTION(
&RTI_LOG_ANY_ss,
"Failed to delete directory: ",
path.c_str());
}
#endif
}
void copy_to(const Path& destination_path)
{
if (!exists()) {
return;
}
try {
std::ifstream src(path_string_.c_str(), std::ios::binary);
std::ofstream dst(
destination_path.path_string_.c_str(),
std::ios::binary);
dst << src.rdbuf();
} catch (std::exception& ex) {
std::stringstream stream;
stream << "Failed to copy file" << std::endl;
stream << " origin : " << path_string_ << std::endl;
stream << " destination: " << destination_path << std::endl;
stream << " error message: " << ex.what() << std::endl;
DDSLog_exception(&RTI_LOG_ANY_s, stream.str().c_str());
}
}
void append_extension(const std::string& extension)
{
path_string_ += extension;
}
/**
* This method detects and returns a (last) "." in the last split of a
* string. The substring after this dot is considered to be the file
* extension.
*/
std::string detect_file_extension()
{
std::string file_extension;
std::vector<std::string> splits = split();
std::string last_split = splits[splits.size() - 1];
std::string::size_type pos = last_split.find_last_of('.');
if (pos != std::string::npos) {
file_extension = last_split.substr(pos + 1);
}
return file_extension;
}
/**
* This method returns the file name (the last split in the path). Depending
* on the parameter, it will strip out the file extension or not.
*/
std::string detect_file_name(bool include_extension = true) const
{
std::vector<std::string> splits = split();
std::string last_split = splits[splits.size() - 1];
if (!include_extension) {
std::string::size_type pos = last_split.find_last_of('.');
if (pos != std::string::npos) {
return last_split.substr(0, pos);
}
}
return last_split;
}
std::vector<std::string> split() const
{
std::vector<std::string> splits;
std::istringstream stream(path_string_);
std::string string_split;
bool prepend_separator = false;
while (getline(stream, string_split, '/')) {
if (string_split.empty()) {
// This can happen if the path is absolute (starts with a
// separator). The first split should contain that separator.
// Signal this with a boolean variable
prepend_separator = true;
continue;
}
if (prepend_separator) {
splits.push_back("/" + string_split);
prepend_separator = false;
} else {
splits.push_back(string_split);
}
}
return splits;
}
static Path from_splits(const std::vector<std::string>& splits)
{
Path path;
for (size_t i = 0; i < splits.size(); i++) {
path += splits[i];
}
return path;
}
uint64_t file_size() const
{
#ifdef RTI_WIN32
HANDLE file_handle = CreateFile(
path_string_.c_str(),
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (file_handle == INVALID_HANDLE_VALUE) {
DDSLog_warn(
&RTI_LOG_ANY_ss,
"Failed to get file size for file: ",
path_string_.c_str());
return 0;
}
LARGE_INTEGER size;
if (!GetFileSizeEx(file_handle, &size)) {
CloseHandle(file_handle);
DDSLog_warn(
&RTI_LOG_ANY_ss,
"Failed to get file size for file: ",
path_string_.c_str());
return 0;
}
CloseHandle(file_handle);
return size.QuadPart;
#else
struct stat buffer;
if (stat(path_string_.c_str(), &buffer) != 0) {
DDSLog_warn(
&RTI_LOG_ANY_ss,
"Failed to get file size for file: ",
path_string_.c_str());
return 0;
}
return (uint64_t) buffer.st_size;
#endif
}
#if !defined(RTI_INTY)
/*
* CORE-10727 [Antonio L]:
* cwd() is not supported in all the architectures. We have to
* port it.
*/
static Path cwd()
{
char working_directory[RTI_OSAPI_STRING_SEQ_STRING_MAX_SIZE] = { 0 };
bool no_error = false;
no_error =
(RTIOsapiUtility_getCurrentDirectory(
working_directory,
RTI_OSAPI_STRING_SEQ_STRING_MAX_SIZE)
!= NULL);
if (!no_error) {
int errnum = RTIOsapiUtility_getError();
char error_string[RTI_OSAPI_ERROR_STRING_MAX_SIZE] = { 0 };
RTI_THROW_SERVICE_EXCEPTION(
&RTI_LOG_OS_FAILURE_sXs,
"getcwd",
errnum,
RTIOsapiUtility_getErrorString(
error_string,
RTI_OSAPI_ERROR_STRING_MAX_SIZE,
errnum));
}
return Path(working_directory);
}
#endif
private:
std::string path_string_;
#define SEPARATOR_STR "/"
/**
* Converter Windows-style separators into Unix-style ones
*/
std::string normalize(const std::string& path)
{
std::string normalized(path);
const std::string SEPARATOR(SEPARATOR_STR);
std::string::size_type pos = normalized.find_first_of('\\');
while (pos != std::string::npos) {
normalized = normalized.replace(pos, 1, SEPARATOR);
pos = normalized.find_first_of('\\', pos + 1);
}
return normalized;
}
std::string remove_trailing_separators(const std::string& path)
{
std::string modified(normalize(path));
if (!modified.empty() && modified.at(modified.size() - 1) == '/') {
modified.erase(modified.size() - 1);
}
return modified;
}
std::string remove_double_separators(std::string& path)
{
const std::string DOUBLE_SEPARATOR_STR("//");
std::string modified(normalize(path));
std::string::size_type pos = modified.find(DOUBLE_SEPARATOR_STR);
while (pos != std::string::npos) {
modified = modified.replace(
pos,
DOUBLE_SEPARATOR_STR.size(),
SEPARATOR_STR);
pos = modified.find(DOUBLE_SEPARATOR_STR, pos + 1);
}
return modified;
}
static int remove_file(
const char *fpath,
const struct stat *sb,
int tflag,
struct FTW *ftwbuf)
{
RTIOsapiUtility_unusedParameter(sb);
RTIOsapiUtility_unusedParameter(tflag);
RTIOsapiUtility_unusedParameter(ftwbuf);
#ifdef RTI_QNX
/**
* In QNX we create the working directory in the NFS. Because the NFS
* have important delays, if we try to remove all the directories,
* the remove will fail because the NFS will accept the request to
* remove the files, but they won't be removed once we reach the upper
* level. Therefore, we will not remove the directories of the QNX
* testers
*/
struct stat buf;
int err = stat(fpath, &buf);
errno = 0;
if (err == 0 && (buf.st_mode & _S_IFMT) != S_IFDIR)
errno = remove(fpath);
return errno;
#else
return remove(fpath);
#endif
}
};
}}} // namespace rti::apputils::util
#endif /* HPP_APPUTILS_PATH_HPP_ */
@@ -0,0 +1,122 @@
/*
* (c) Copyright, Real-Time Innovations, 2016-.
* All rights reserved.
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef APPUTILS_HPP_
#define APPUTILS_HPP_
// IMPORTANT: macros.hpp must be the first RTI header included in every header
// file so that symbols are exported correctly on Windows
#include <dds/core/macros.hpp>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <limits>
#include <string>
#include <sstream>
#include "osapi/osapi_utility.h"
#include <dds/core/types.hpp>
#include <rti/core/Exception.hpp>
#include "dds/core/Time.hpp"
#define STATIC_CONST_STRING_DEFINITION(classname, name, value) \
const std::string& classname::name() \
{ \
static std::string name##var(value); \
return name##var; \
}
#define RECORDER_FILE_PATH_MAX_LENGTH (4096)
namespace rti { namespace apputils { namespace util {
constexpr int64_t g_int64_max = 0x7FFFFFFFFFFFFFFFULL;
constexpr uint64_t g_uint64_max = 0xFFFFFFFFFFFFFFFFULL;
inline
std::string normalize_path(const std::string& file_name)
{
char normalized[RECORDER_FILE_PATH_MAX_LENGTH] = {'\0'};
if (!RTIOsapiUtility_normalizePath(
normalized,
RECORDER_FILE_PATH_MAX_LENGTH,
file_name.c_str())) {
std::stringstream error_msg;
error_msg << "!normalize path " << file_name.c_str() << "\n";
throw dds::core::IllegalOperationError(error_msg.str());
}
return normalized;
}
inline
int64_t convert_uint64_into_int64(uint64_t value)
{
int64_t signed_int64 = 0;
if (value > g_int64_max) {
signed_int64 = g_int64_max;
} else {
signed_int64 = (int64_t) value;
}
return signed_int64;
}
inline
uint64_t to_nanosecs(dds::core::Time time)
{
uint64_t nanosec;
try {
nanosec = time.to_nanosecs();
} catch (std::overflow_error&) {
nanosec = g_uint64_max;
}
return nanosec;
}
/**
* @brief Check if the sum of two values would produce an overflow.
*
* @tparam T type of the elements to be summed.
*
* @param lhs Left-hand side of the sum
* @param rhs Right-hand side of the sum
*
* @return true if the sum overflows, false otherwise.
*/
template <typename T>
bool sum_overflows(T lhs, T rhs)
{
return lhs > 0 && rhs > 0 && (std::numeric_limits<T>::max)() - lhs < rhs;
}
/**
* @brief Check if the sum of two values would produce an underflow.
*
* @tparam T type of the elements to be summed.
*
* @param lhs Left-hand side of the sum
* @param rhs Right-hand side of the sum
*
* @return true if the sum underflows, false otherwise.
*/
template <typename T>
bool sum_underflows(T lhs, T rhs)
{
return lhs < 0 && rhs < 0 && std::numeric_limits<T>::lowest() - lhs > rhs;
}
} } } // namespace rti:apputils::util
#endif /* APPUTILS_HPP_ */
@@ -0,0 +1,155 @@
/*
(c) Copyright, Real-Time Innovations, 2018.
All rights reserved.
No duplications, whole or partial, manual or electronic, may be made
without express written permission. Any such copies, or
revisions thereof, must display this notice unaltered.
This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef HPP_RTI_APPUTILS_VERSIONABLECOMPONENT_HPP_
#define HPP_RTI_APPUTILS_VERSIONABLECOMPONENT_HPP_
// IMPORTANT: macros.hpp must be the first RTI header included in every header
// file so that symbols are exported correctly on Windows
#include <dds/core/macros.hpp>
#include <sstream>
#include <string>
#include <stdint.h>
#include <assert.h>
#include "dds/core/Exception.hpp"
namespace rti { namespace apputils { namespace util {
class ComponentVersion {
public:
constexpr ComponentVersion() noexcept :
major_(0),
minor_(0)
{ }
constexpr ComponentVersion(const uint8_t major, const uint8_t minor) noexcept :
major_(major),
minor_(minor)
{ }
ComponentVersion(const std::string& version_str)
{
std::istringstream stream(version_str);
int16_t value;
stream >> value;
if (stream.fail()) {
throw dds::core::InvalidArgumentError(version_string_error(version_str));
}
major_ = static_cast<uint8_t>(value);
char separator;
stream >> separator;
if (stream.fail() || separator != '.') {
throw dds::core::InvalidArgumentError(version_string_error(version_str));
}
stream >> value;
if (stream.fail()) {
throw dds::core::InvalidArgumentError(version_string_error(version_str));
}
minor_ = static_cast<uint8_t>(value);
}
uint8_t get_major() const noexcept { return major_; }
uint8_t get_minor() const noexcept { return minor_; }
bool operator ==(const ComponentVersion& other) const noexcept
{
return (major_ == other.major_ && minor_ == other.minor_);
}
bool operator !=(const ComponentVersion& other) const noexcept
{
return !(*this == other);
}
bool operator < (const ComponentVersion& other) const noexcept
{
if ((int16_t) major_ - (int16_t) other.major_ == 0) {
return ((int16_t) minor_ - (int16_t) other.minor_ < 0);
} else {
return ((int16_t) major_ - (int16_t) other.major_ < 0);
}
}
bool operator > (const ComponentVersion& other) const noexcept
{
if ((int16_t) major_ - (int16_t) other.major_ == 0) {
return ((int16_t) minor_ - (int16_t) other.minor_ > 0);
} else {
return ((int16_t) major_ - (int16_t) other.major_ > 0);
}
}
operator std::string () const noexcept
{
return std::to_string(major_) + "." + std::to_string(minor_);
}
private:
uint8_t major_;
uint8_t minor_;
std::string version_string_error (const std::string & str) const noexcept
{
return "VersionableComponent: invalid version string: " + str;
}
};
/**
* @brief A versionable component has a name and a version and it's used to
* keep track of the versions of the components used to record data.
* This information is kept in the 'metadata' database file. It can later
* be recovered by Replay/Converter to use exactly the same components'
* versions that were when recording.
* @param VersionType The version object to be used. By default, the Version
* type implemented above is used (a simple, two-digit, stringifyable
* version type).
*/
template<typename VersionType>
class VersionableComponent {
public:
VersionableComponent(
std::string component_name,
const VersionType& version) :
component_name_(component_name),
version_(version) { }
const VersionType& version() const { return version_; }
const std::string& component_name() const { return component_name_; }
void verify_version(const VersionType& version) const
{
if (version > version_) {
std::stringstream error_stream;
error_stream << "VersionableComponent: " << component_name_.c_str()
<< ": detected newer version. The current version of the "
<< "component in this tool ("
<< ((const std::string&) version_).c_str()
<< ") is unable to read the newer "
<< component_name_.c_str() <<" version ("
<< ((const std::string&) version).c_str() <<") found ";
throw dds::core::IllegalOperationError(error_stream.str());
}
}
private:
std::string component_name_;
VersionType version_;
};
typedef VersionableComponent<ComponentVersion> DefaultVersionableComponent;
} } } /* namespace rti::apputils::util */
#endif /* HPP_RTI_APPUTILS_VERSIONABLECOMPONENT_HPP_ */