This commit is contained in:
2025-05-13 01:34:53 +03:00
parent 427735e23d
commit 83f3f1c7d4
945 changed files with 633484 additions and 0 deletions
@@ -0,0 +1,145 @@
#ifndef __FIBRE_ASYNC_STREAM_HPP
#define __FIBRE_ASYNC_STREAM_HPP
#include <fibre/bufptr.hpp>
#include <fibre/callback.hpp>
#include <stdint.h>
namespace fibre {
enum StreamStatus {
kStreamOk,
kStreamCancelled,
kStreamClosed,
kStreamError
};
struct ReadResult {
StreamStatus status;
/**
* @brief The pointer to one position after the last byte that was
* transferred.
* This must always be in [buffer.begin(), buffer.end()], even if the
* transfer was not succesful.
* If the status is kStreamError or kStreamCancelled then the accuracy
* of this field is not guaranteed.
*/
unsigned char* end;
};
struct WriteResult {
StreamStatus status;
/**
* @brief The pointer to one position after the last byte that was
* transferred.
* This must always be in [buffer.begin(), buffer.end()], even if the
* transfer was not succesful.
* If the status is kStreamError or kStreamCancelled then the accuracy
* of this field is not guaranteed.
*/
const unsigned char* end;
};
using TransferHandle = uintptr_t;
/**
* @brief Base class for asynchronous stream sources.
*/
class AsyncStreamSource {
public:
/**
* @brief Starts a read operation. Once the read operation completes,
* on_finished.complete() is called.
*
* Most implementations only allow one transfer to be active at a time.
*
* TODO: specify if `completer` can be called directly within this function.
*
* @param buffer: The buffer where the data to be written shall be fetched from.
* Must remain valid until `completer` is satisfied.
* @param handle: The variable pointed to by this argument is set to an
* opaque transfer handle that can be passed to cancel_read() as
* long as the operation has not yet completed.
* If the completer is invoked directly from start_read() then the
* handle is not modified after this invokation. That means it's safe
* for the completion handler to reuse the handle variable.
* @param completer: The completer that will be completed once the operation
* finishes, whether successful or not.
* Must remain valid until it is satisfied.
*/
virtual void start_read(bufptr_t buffer, TransferHandle* handle, Callback<void, ReadResult> completer) = 0;
/**
* @brief Cancels an operation that was previously started with start_read().
*
* The transfer is cancelled asynchronously and the associated completer
* will eventually be completed with kStreamCancelled. Until then the
* transfer must be considered still in progress and associated resources
* must not be freed.
*
* TODO: specify if an implementation is allowed to return something other
* than kStreamCancelled when the transfer was cancelled.
*
* This function must not be called once the stream has started to invoke
* the associated completion handler. It must also not be called twice for
* the same transfer.
*/
virtual void cancel_read(TransferHandle transfer_handle) = 0;
};
/**
* @brief Base class for asynchronous stream sources.
*
* Thread-safety: Implementations are generally not required to provide thread
* safety. Users should only call the functions of this class on the same thread
* as the event loop on which the stream runs.
*/
class AsyncStreamSink {
public:
/**
* @brief Starts a write operation. Once the write operation completes,
* on_finished.complete() is called.
*
* Most implementations only allow one transfer to be active at a time.
*
* TODO: specify if `completer` can be called directly within this function.
*
* @param buffer: The buffer where the data to be written shall be fetched from.
* Must remain valid until `completer` is satisfied.
* @param handle: The variable pointed to by this argument is set to an
* opaque transfer handle that can be passed to cancel_write() as
* long as the operation has not yet completed.
* If the completer is invoked directly from start_write() then the
* handle is not modified after this invokation. That means it's safe
* for the completion handler to reuse the handle variable.
* @param completer: The completer that will be completed once the operation
* finishes, whether successful or not.
* Must remain valid until it is satisfied.
*/
virtual void start_write(cbufptr_t buffer, TransferHandle* handle, Callback<void, WriteResult> completer) = 0;
/**
* @brief Cancels an operation that was previously started with start_write().
*
* The transfer is cancelled asynchronously and the associated completer
* will eventually be completed with kStreamCancelled. Until then the
* transfer must be considered still in progress and associated resources
* must not be freed.
*
* TODO: specify if an implementation is allowed to return something other
* than kStreamCancelled when the transfer was cancelled.
*
* This function must not be called once the stream has started to invoke
* the associated completion handler. It must also not be called twice for
* the same transfer.
*/
virtual void cancel_write(TransferHandle transfer_handle) = 0;
};
}
#endif // __FIBRE_ASYNC_STREAM_HPP
@@ -0,0 +1,100 @@
#ifndef __FIBRE_BUFPTR_HPP
#define __FIBRE_BUFPTR_HPP
#include <stdlib.h>
#include <vector>
namespace fibre {
static inline bool soft_assert(bool expr) { return expr; } // TODO: implement
/**
* @brief Holds a reference to a buffer and a length.
* Since this class implements begin() and end(), you can use it with many
* standard algorithms that operate on iterable objects.
*/
template<typename T>
struct generic_bufptr_t {
using iterator = T*;
using const_iterator = const T*;
generic_bufptr_t(T* begin, size_t length) : begin_(begin), end_(begin + length) {}
generic_bufptr_t(T* begin, T* end) : begin_(begin), end_(end) {}
generic_bufptr_t() : begin_(nullptr), end_(nullptr) {}
template<size_t I>
generic_bufptr_t(T (&begin)[I]) : generic_bufptr_t(begin, I) {}
generic_bufptr_t(std::vector<typename std::remove_const<T>::type>& vector)
: generic_bufptr_t(vector.data(), vector.size()) {}
generic_bufptr_t(const std::vector<typename std::remove_const<T>::type>& vector)
: generic_bufptr_t(vector.data(), vector.size()) {}
generic_bufptr_t(const generic_bufptr_t<typename std::remove_const<T>::type>& other)
: generic_bufptr_t(other.begin(), other.end()) {}
generic_bufptr_t& operator+=(size_t num) {
if (!soft_assert(num <= size())) {
num = size();
}
begin_ += num;
return *this;
}
generic_bufptr_t operator++(int) {
generic_bufptr_t result = *this;
*this += 1;
return result;
}
T& operator*() {
return *begin_;
}
generic_bufptr_t take(size_t num) const {
if (!soft_assert(num <= size())) {
num = size();
}
generic_bufptr_t result = {begin_, num};
return result;
}
generic_bufptr_t skip(size_t num, size_t* processed_bytes = nullptr) const {
if (!soft_assert(num <= size())) {
num = size();
}
if (processed_bytes)
(*processed_bytes) += num;
return {begin_ + num, end_};
}
size_t size() const {
return end_ - begin_;
}
bool empty() const {
return size() == 0;
}
T*& begin() { return begin_; }
T*& end() { return end_; }
T* const & begin() const { return begin_; }
T* const & end() const { return end_; }
T& front() const { return *begin(); }
T& back() const { return *(end() - 1); }
T& operator[](size_t idx) { return *(begin() + idx); }
private:
T* begin_;
T* end_;
};
using cbufptr_t = generic_bufptr_t<const unsigned char>;
using bufptr_t = generic_bufptr_t<unsigned char>;
}
#endif // __FIBRE_BUFPTR_HPP
@@ -0,0 +1,126 @@
#ifndef __CALLBACK_HPP
#define __CALLBACK_HPP
#include <stdlib.h>
#include <typeinfo>
#include <tuple>
#include <functional>
#include <type_traits>
namespace fibre {
namespace detail {
template<typename T> struct get_default { static T val() { return {}; } };
template<> struct get_default<void> { static void val() {} };
}
template<typename TRet, typename ... TArgs>
class Callback {
public:
Callback() : cb_(nullptr), ctx_(nullptr) {}
Callback(std::nullptr_t) : cb_(nullptr), ctx_(nullptr) {}
Callback(TRet(*callback)(void*, TArgs...), void* ctx) :
cb_(callback), ctx_(ctx) {}
/**
* @brief Creates a copy of another Callback instance.
*
* This is only works it the other callback has identical template parameters.
* This constructor is templated so that construction from an incompatible
* callback gives a useful error message.
*/
//template<typename TRetOther, typename ... TArgsOther>
//Callback(const Callback<TRetOther, TArgsOther...>& other) : cb_(other.cb_), ctx_(other.ctx_) {
// static_assert(std::is_same<Callback<TRetOther, TArgsOther...>, Callback>::value, "incompatible callback type");
//}
Callback(const Callback& other) : cb_(other.cb_), ctx_(other.ctx_) {}
// If you get a compile error "[...] invokes a deleted function" that points
// here then you're probably trying to assign a Callback with incompatible
// template arguments to another Callback.
template<typename TRetOther, typename ... TArgsOther>
Callback(const Callback<TRetOther, TArgsOther...>& other) = delete;
/**
* @brief Constructs a callback object from a functor. The functor must
* remain allocated throughout the lifetime of the Callback.
*/
template<typename TFunc>
Callback(const TFunc& func) :
cb_([](void* ctx, TArgs...args){
return (*(const TFunc*)ctx)(args...);
}), ctx_((void*)&func) {}
operator bool() {
return cb_;
}
TRet invoke(TArgs ... arg) const {
if (cb_) {
return (*cb_)(ctx_, arg...);
}
return detail::get_default<TRet>::val();
}
TRet invoke_and_clear(TArgs ... arg) {
void* ctx = ctx_;
auto cb = cb_;
ctx_ = nullptr;
cb_ = nullptr;
if (cb) {
return (*cb)(ctx, arg...);
}
return detail::get_default<TRet>::val();
}
typedef TRet(*cb_t)(void*, TArgs...);
cb_t get_ptr() { return cb_; }
void* get_ctx() { return ctx_; }
private:
TRet(*cb_)(void*, TArgs...);
void* ctx_;
};
template<typename _TRet, typename _TObj, typename ... _TArgs>
struct function_traits {
using TRet = _TRet;
using TArgs = std::tuple<_TArgs...>;
using TObj = _TObj;
};
template<typename _TRet, typename _TObj, typename ... _TArgs>
function_traits<_TRet, _TObj, _TArgs...> make_function_traits(_TRet (_TObj::*)(_TArgs...)) {
return {};
}
template<typename T1, T1 T2, typename T3, typename T4, typename T5>
struct MemberCallback;
template<typename T, T func, typename TObj, typename TRes, typename ... TArgs>
struct MemberCallback<T, func, TObj, TRes, std::tuple<TArgs...>> {
using cb_t = Callback<TRes, TArgs...>;
static cb_t with(TObj* obj) {
return cb_t{[](void* obj, TArgs... arg) {
return (((TObj*)obj)->*func)(arg...);
}, obj};
}
};
template<typename T, T func,
typename TTraits = decltype(make_function_traits(func)),
typename MemCb = MemberCallback<T, func, typename TTraits::TObj, typename TTraits::TRet, typename TTraits::TArgs>>
typename MemCb::cb_t make_callback(typename TTraits::TObj* obj) {
return MemCb::with(obj);
}
#define MEMBER_CB(obj, func) \
fibre::make_callback< \
decltype(&std::remove_reference_t<decltype(*obj)>::func), \
&std::remove_reference_t<decltype(*obj)>::func \
>(obj)
}
#endif // __CALLBACK_HPP
@@ -0,0 +1,38 @@
#ifndef __FIBRE_CHANNEL_DISCOVERER
#define __FIBRE_CHANNEL_DISCOVERER
#include "async_stream.hpp"
#include <fibre/callback.hpp>
#include <fibre/status.hpp>
namespace fibre {
struct ChannelDiscoveryResult {
Status status;
AsyncStreamSource* rx_channel;
AsyncStreamSink* tx_channel;
size_t mtu;
};
struct ChannelDiscoveryContext {};
class Domain; // defined in fibre.hpp
class ChannelDiscoverer {
public:
// TODO: maybe we should remove "handle" because a discovery can also be
// uniquely identified by domain.
virtual void start_channel_discovery(
Domain* domain,
const char* specs, size_t specs_len,
ChannelDiscoveryContext** handle) = 0;
virtual int stop_channel_discovery(ChannelDiscoveryContext* handle) = 0;
protected:
bool try_parse_key(const char* begin, const char* end, const char* key, const char** val_begin, const char** val_end);
bool try_parse_key(const char* begin, const char* end, const char* key, int* val);
};
}
#endif // __FIBRE_CHANNEL_DISCOVERER
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,74 @@
#ifndef __FIBRE_EVENT_LOOP_HPP
#define __FIBRE_EVENT_LOOP_HPP
#include "callback.hpp"
#include <stdint.h>
namespace fibre {
struct EventLoopTimer;
/**
* @brief Base class for event loops.
*
* Thread-safety: The public functions of this class except for post() must not
* be assumed to be thread-safe.
* Generally the functions of an event loop are only safe to be called from the
* event loop's thread itself.
*/
class EventLoop {
public:
/**
* @brief Registers a callback for immediate execution on the event loop
* thread.
*
* This function must be thread-safe.
*/
virtual bool post(Callback<void> callback) = 0;
/**
* @brief Registers the given file descriptor on this event loop.
*
* This function is only implemented on Unix-like systems.
*
* @param fd: A waitable Unix file descriptor on which to listen for events.
* @param events: A bitfield that specifies the events to listen for.
* For instance EPOLLIN or EPOLLOUT.
* @param callback: The callback to invoke every time the event triggers.
* A bitfield is passed to the callback to indicate which events were
* triggered. This callback must remain valid until
* deregister_event() is called for the same file descriptor.
*/
virtual bool register_event(int fd, uint32_t events, Callback<void, uint32_t> callback) = 0;
/**
* @brief Deregisters the given event.
*
* Once this function returns, the associated callback will no longer be
* invoked and its resources can be freed.
*/
virtual bool deregister_event(int fd) = 0;
/**
* @brief Registers a callback to be called at a later point in time.
*
* This returns an opaque handler which can be used to cancel the timer.
*
* @param delay: The delay from now in seconds.
* TOOD: specify if OS sleep time is counted in.
*/
virtual struct EventLoopTimer* call_later(float delay, Callback<void> callback) = 0;
/**
* @brief Cancels a timer which was previously started by call_later().
*
* Must not be called after invokation of the callback has started.
* This also means that cancel_timer() must not be called from within the
* callback of the timer itself.
*/
virtual bool cancel_timer(EventLoopTimer* timer) = 0;
};
}
#endif // __FIBRE_EVENT_LOOP_HPP
@@ -0,0 +1,155 @@
#ifndef __FIBRE_HPP
#define __FIBRE_HPP
#include <fibre/callback.hpp>
#include <fibre/bufptr.hpp>
#include <fibre/cpp_utils.hpp>
#include <fibre/event_loop.hpp>
#include <fibre/channel_discoverer.hpp>
#include <string>
#include <memory>
#if FIBRE_ENABLE_LIBUSB_BACKEND
#include "../../platform_support/libusb_transport.hpp"
#endif
#if FIBRE_ENABLE_TCP_CLIENT_BACKEND
#include "../../platform_support/posix_tcp_backend.hpp"
#endif
namespace fibre {
struct CallBuffers {
Status status;
cbufptr_t tx_buf;
bufptr_t rx_buf;
};
struct CallBufferRelease {
Status status;
const uint8_t* tx_end;
uint8_t* rx_end;
};
struct Function {
virtual std::optional<CallBufferRelease>
call(void**, CallBuffers, Callback<std::optional<CallBuffers>, CallBufferRelease>) = 0;
};
struct Object;
struct Interface;
class Domain;
template<typename T>
struct StaticBackend {
std::string name;
T impl;
};
struct Context {
size_t n_domains = 0;
EventLoop* event_loop;
std::tuple<
#if FIBRE_ENABLE_LIBUSB_BACKEND
LibusbDiscoverer
#endif
#if FIBRE_ENABLE_LIBUSB_BACKEND && FIBRE_ENABLE_TCP_CLIENT_BACKEND
, // TODO: find a less awkward way to do this
#endif
#if FIBRE_ENABLE_TCP_CLIENT_BACKEND
PosixTcpClientBackend
#endif
#if FIBRE_ENABLE_TCP_CLIENT_BACKEND && FIBRE_ENABLE_TCP_SERVER_BACKEND
, // TODO: find a less awkward way to do this
#endif
#if FIBRE_ENABLE_TCP_SERVER_BACKEND
PosixTcpServerBackend
#endif
> static_backends;
#if FIBRE_ALLOW_HEAP
std::unordered_map<std::string, ChannelDiscoverer*> discoverers;
#endif
/**
* @brief Creates a domain on which objects can subsequently be published
* and discovered.
*
* This potentially starts looking for channels on this domain.
*/
Domain* create_domain(std::string specs);
void close_domain(Domain* domain);
void register_backend(std::string name, ChannelDiscoverer* backend);
void deregister_backend(std::string name);
};
// TODO: don't declare these types here
struct LegacyProtocolPacketBased;
class LegacyObjectClient;
struct LegacyObject;
class Domain {
friend struct Context;
public:
#if FIBRE_ENABLE_CLIENT
// TODO: add interface argument
// TODO: support multiple discovery instances
void start_discovery(Callback<void, Object*, Interface*> on_found_object, Callback<void, Object*> on_lost_object);
void stop_discovery();
#endif
void add_channels(ChannelDiscoveryResult result);
Context* ctx;
private:
#if FIBRE_ENABLE_CLIENT
void on_found_root_object(LegacyObjectClient* obj_client, std::shared_ptr<LegacyObject> obj);
void on_lost_root_object(LegacyObjectClient* obj_client, std::shared_ptr<LegacyObject> obj);
#endif
void on_stopped(LegacyProtocolPacketBased* protocol, StreamStatus status);
#if FIBRE_ALLOW_HEAP
std::unordered_map<std::string, fibre::ChannelDiscoveryContext*> channel_discovery_handles;
#endif
#if FIBRE_ENABLE_CLIENT
Callback<void, Object*, Interface*> on_found_object_;
Callback<void, Object*> on_lost_object_;
std::unordered_map<Object*, Interface*> root_objects_;
#endif
};
/**
* @brief Opens and initializes a Fibre context.
*
* If FIBRE_ALLOW_HEAP=0 only one Fibre context can be open at a time.
*
* @returns: A non-null pointer on success, null otherwise.
*/
Context* open(EventLoop* event_loop);
void close(Context*);
/**
* @brief Launches an event loop on the current thread.
*
* This function returns when the event loop becomes empty.
*
* If FIBRE_ALLOW_HEAP=0 only one event loop can be running at a time.
*
* This function returns false if Fibre was compiled with
* FIBRE_ENABLE_EVENT_LOOP=0.
*
* @param on_started: This function is the first event that is placed on the
* event loop. This function usually creates further events, for instance
* by calling open().
* @returns: true if the event loop ran to completion. False if this function is
* not implemented on this operating system or if another error
* occurred.
*/
bool launch_event_loop(Callback<void, EventLoop*> on_started);
}
#endif // __FIBRE_HPP
@@ -0,0 +1,219 @@
#ifndef __FIBRE_INTROSPECTION_HPP
#define __FIBRE_INTROSPECTION_HPP
#include <stdlib.h>
#include <algorithm>
#include <cstring>
#pragma GCC push_options
#pragma GCC optimize ("s")
class TypeInfo;
class Introspectable;
using introspectable_storage_t = std::aligned_storage<4 * sizeof(uintptr_t), sizeof(uintptr_t)>::type;
struct PropertyInfo {
const char * name;
const TypeInfo* type_info;
};
/**
* @brief Contains runtime accessible type information.
*
* Specifically, this information consists of a list of PropertyInfo items which
* enable accessing attributes of an object by a runtime string.
*
* Typically, for each combination of C++ type and Fibre interface implemented
* by this type, one (static constant) TypeInfo object will exist.
*/
class TypeInfo {
friend class Introspectable;
public:
TypeInfo(const PropertyInfo* property_table, size_t property_table_length)
: property_table_(property_table), property_table_length_(property_table_length) {}
virtual introspectable_storage_t get_child(introspectable_storage_t obj, size_t idx) const = 0;
Introspectable get_child(const Introspectable& obj, const char * name, size_t length) const;
protected:
template<typename T> static T& as(Introspectable& obj);
template<typename T> static const T& as(const Introspectable& obj);
template<typename T> static Introspectable make_introspectable(T obj, const TypeInfo* type_info);
private:
const PropertyInfo* property_table_;
size_t property_table_length_;
};
/**
* @brief Wraps a reference to an application object by attaching runtime
* accessible type information.
*
* The reference that is wrapped is typically a pointer but can also be a small
* temporary, on-demand constructed object such as a fibre::Property<...> which
* contains multiple pointers.
*/
class Introspectable {
friend class TypeInfo;
public:
Introspectable() {}
/**
* @brief Returns an Introspectable object for the attribute referenced by
* the specified attribute name.
*
* The name can consist of multiple parts separated by dots.
*
* If the attribute does not exist, an invalid Introspectable is returned.
*
* @param path: The name or path of the attribute.
* @param length: The maximum length of the name.
*/
Introspectable get_child(const char * path, size_t length) {
Introspectable current = *this;
const char * begin = path;
const char * end = std::find(begin, path + length, '\0');
while ((begin < end) && current.type_info_) {
const char * end_of_token = std::find(begin, end, '.');
current = current.get_direct_child(begin, end_of_token - begin);
begin = std::min(end, end_of_token + 1);
}
return current;
};
bool is_valid() {
return type_info_;
}
const TypeInfo* get_type_info() {
return type_info_;
}
private:
Introspectable get_direct_child(const char * name, size_t length) const {
for (size_t i = 0; i < type_info_->property_table_length_; ++i) {
if (!strncmp(name, type_info_->property_table_[i].name, length) && (length == strlen(type_info_->property_table_[i].name))) {
Introspectable result;
result.storage_ = type_info_->get_child(storage_, i);
result.type_info_ = type_info_->property_table_[i].type_info;
return result;
}
}
return {};
}
public: // these should technically be protected but are public for optimization reasons
// We use this storage to hold generic small objects. Usually that's a pointer
// but sometimes it's an on-demand constructed Property<...>.
// Caution: only put objects in here which are trivially copyable, movable
// and destructible as any custom operation wouldn't be called.
introspectable_storage_t storage_;
const TypeInfo* type_info_ = nullptr;
};
template<typename T> T& TypeInfo::as(Introspectable& obj) {
static_assert(sizeof(T) <= sizeof(obj.storage_), "invalid size");
return *(T*)&obj.storage_;
}
template<typename T> const T& TypeInfo::as(const Introspectable& obj) {
static_assert(sizeof(T) <= sizeof(obj.storage_), "invalid size");
return *(const T*)&obj.storage_;
}
template<typename T> Introspectable TypeInfo::make_introspectable(T obj, const TypeInfo* type_info) {
Introspectable introspectable;
as<T>(introspectable) = obj;
introspectable.type_info_ = type_info;
return introspectable;
}
// maybe_underlying_type_t<T> resolves to the underlying type of T if T is an enum type or otherwise to T itself.
template<typename T, bool = std::is_enum<T>::value> struct maybe_underlying_type;
template<typename T> struct maybe_underlying_type<T, true> { typedef std::underlying_type_t<T> type; };
template<typename T> struct maybe_underlying_type<T, false> { typedef T type; };
template<typename T> using maybe_underlying_type_t = typename maybe_underlying_type<T>::type;
struct StringConvertibleTypeInfo {
virtual bool get_string(const Introspectable& obj, char* buffer, size_t length) const { return false; }
virtual bool set_string(const Introspectable& obj, char* buffer, size_t length) const { return false; }
};
struct FloatSettableTypeInfo {
//virtual bool get_float(const Introspectable& obj, float* val) const { return false; }
virtual bool set_float(const Introspectable& obj, float val) const { return false; }
};
/* Built-in type infos ********************************************************/
template<typename T>
struct FibrePropertyTypeInfo;
// readonly property
template<typename T>
struct FibrePropertyTypeInfo<Property<const T>> : StringConvertibleTypeInfo, TypeInfo {
using TypeInfo::TypeInfo;
static const PropertyInfo property_table[];
static const FibrePropertyTypeInfo<Property<const T>> singleton;
introspectable_storage_t get_child(introspectable_storage_t obj, size_t idx) const override {
return {};
}
bool get_string(const Introspectable& obj, char* buffer, size_t length) const override {
return to_string(static_cast<maybe_underlying_type_t<T>>(as<const Property<const T>>(obj).read()), buffer, length, 0);
}
};
template<typename T>
const PropertyInfo FibrePropertyTypeInfo<Property<const T>>::property_table[] = {};
template<typename T>
const FibrePropertyTypeInfo<Property<const T>> FibrePropertyTypeInfo<Property<const T>>::singleton{FibrePropertyTypeInfo<Property<const T>>::property_table, sizeof(FibrePropertyTypeInfo<Property<const T>>::property_table) / sizeof(FibrePropertyTypeInfo<Property<const T>>::property_table[0])};
// readwrite property
template<typename T>
struct FibrePropertyTypeInfo<Property<T>> : FloatSettableTypeInfo, StringConvertibleTypeInfo, TypeInfo {
using TypeInfo::TypeInfo;
static const PropertyInfo property_table[];
static const FibrePropertyTypeInfo<Property<T>> singleton;
static const Introspectable make_introspectable(Property<T> obj) { return TypeInfo::make_introspectable(obj, &singleton); }
introspectable_storage_t get_child(introspectable_storage_t obj, size_t idx) const override {
return {};
}
bool get_string(const Introspectable& obj, char* buffer, size_t length) const override {
return to_string(static_cast<maybe_underlying_type_t<T>>(as<const Property<T>>(obj).read()), buffer, length, 0);
}
bool set_string(const Introspectable& obj, char* buffer, size_t length) const override {
maybe_underlying_type_t<T> value{};
if (!from_string(buffer, length, &value, 0)) {
return false;
}
as<const Property<T>>(obj).exchange(static_cast<T>(value));
return true;
}
bool set_float(const Introspectable& obj, float val) const override {
maybe_underlying_type_t<T> value{};
if (!conversion::set_from_float(val, &value)) {
return false;
}
as<const Property<T>>(obj).exchange(static_cast<T>(value));
return true;
}
};
template<typename T>
const PropertyInfo FibrePropertyTypeInfo<Property<T>>::property_table[] = {};
template<typename T>
const FibrePropertyTypeInfo<Property<T>> FibrePropertyTypeInfo<Property<T>>::singleton{FibrePropertyTypeInfo<Property<T>>::property_table, sizeof(FibrePropertyTypeInfo<Property<T>>::property_table) / sizeof(FibrePropertyTypeInfo<Property<T>>::property_table[0])};
#pragma GCC pop_options
#endif // __FIBRE_INTROSPECTION_HPP
@@ -0,0 +1,603 @@
/**
* @brief Fibre C library
*
* The library is fully asynchronous and runs on an application-managed event
* loop. This integration happens with the call to libfibre_open(), where the
* application must pass a couple of functions that libfibre will use to put
* tasks on the event loop.
*
* Some general things to note:
* - None of the library's functions are blocking.
* - None of the library's functions can be expected to be thread-safe, they
* should not be invoked from any other thread than the one that runs the
* event loop.
* - Callbacks that the user passes to a libfibre function are always executed
* on the event loop thread.
* - All of the library's functions can be expected reentry-safe. That means
* you can call into any libfibre function from any callback handler that
* libfibre invokes.
*/
#ifndef __LIBFIBRE_H
#define __LIBFIBRE_H
#include <stdint.h>
#include <stdlib.h>
#if defined(_MSC_VER)
# define DLL_EXPORT __declspec(dllexport)
# define DLL_IMPORT __declspec(dllimport)
#elif defined(__GNUC__)
# define DLL_EXPORT __attribute__((visibility("default")))
# define DLL_IMPORT
# if __GNUC__ > 4
# define DLL_LOCAL __attribute__((visibility("hidden")))
# else
# define DLL_LOCAL
# endif
#else
# error("Don't know how to export shared object libraries")
#endif
#ifdef FIBRE_COMPILE
# ifndef FIBRE_PUBLIC
# define FIBRE_PUBLIC DLL_EXPORT
# endif
# define FIBRE_PRIVATE DLL_LOCAL
#else
# define FIBRE_PUBLIC DLL_IMPORT
#endif
#define FIBRE_PRIVATE DLL_LOCAL
#ifdef __cplusplus
extern "C" {
#endif
struct LibFibreCtx;
struct LibFibreDiscoveryCtx;
struct LibFibreCallContext;
struct LibFibreObject;
struct LibFibreInterface;
struct LibFibreFunction;
struct LibFibreAttribute;
struct LibFibreTxStream;
struct LibFibreRxStream;
struct LibFibreDomain;
// This enum must remain identical to fibre::Status.
enum LibFibreStatus {
kFibreOk,
kFibreBusy, //<! The request will complete asynchronously
kFibreCancelled, //!< The operation was cancelled due to a request by the application or the remote peer
kFibreClosed, //!< The operation has finished orderly or shall be finished orderly
kFibreInvalidArgument, //!< Bug in the application
kFibreInternalError, //!< Bug in the local fibre implementation
kFibreProtocolError, //!< A remote peer is misbehaving (indicates bug in the remote peer)
kFibreHostUnreachable, //!< The remote peer can no longer be reached
//kFibreInsufficientData, // maybe we will introduce this to tell the caller that the granularity of the data is too small
};
struct LibFibreVersion {
uint16_t major;
uint16_t minor;
uint16_t patch;
};
typedef int (*post_cb_t)(void (*callback)(void*), void* cb_ctx);
typedef int (*register_event_cb_t)(int fd, uint32_t events, void (*callback)(void*, uint32_t), void* cb_ctx);
typedef int (*deregister_event_cb_t)(int fd);
typedef struct EventLoopTimer* (*call_later_cb_t)(float delay, void (*callback)(void*), void* cb_ctx);
typedef int (*cancel_timer_cb_t)(struct EventLoopTimer* timer);
struct LibFibreEventLoop {
/**
* @brief Called by libfibre when it wants the application to run a callback
* on the application's event loop.
*
* This is the only callback that libfibre can invoke from a different
* thread than the event loop thread itself. The application must ensure
* that this callback is thread-safe.
* This allows libfibre to run other threads internally while keeping
* threading promises made to the application.
*/
post_cb_t post;
/**
* @brief TODO: this is a Unix specific callback. Need to use IOCP on Windows.
*/
register_event_cb_t register_event;
/**
* @brief TODO: this is a Unix specific callback. Need to use IOCP on Windows.
*/
deregister_event_cb_t deregister_event;
/**
* @brief Called by libfibre to ask the application to call a certain
* callback after a certain amount of time.
*
* The callback must be invoked on the same thread on which libfibre_open()
* was called. The application should return an opaque handle that
* libfibre can use to cancel the timer.
*/
call_later_cb_t call_later;
/**
* @brief Called by libfibre to ask the application to cancel a callback
* timer previously enqueued with call_later().
*/
cancel_timer_cb_t cancel_timer;
};
/**
* @brief on_start_discovery callback type for libfibre_register_backend().
*
* For every channel pair that the application finds that matches the filter of
* this discoverer the application should call libfibre_add_channels().
*
* @param discovery_handle: An opaque handle that libfibre will pass to the
* corresponding on_stop_discovery callback to stop the discovery.
* @param specs, specs_length: The specs string that specifies discoverer-specific
* filter parameters.
*/
typedef void (*on_start_discovery_cb_t)(void* ctx, LibFibreDomain* domain, const char* specs, size_t specs_length);
typedef void (*on_stop_discovery_cb_t)(void* ctx, LibFibreDomain* domain);
/**
* @brief on_found_object callback type for libfibre_start_discovery().
* @param obj: The object handle.
* @param intf: The interface handle. Valid for as long as any handle of an
* object that implements it is valid.
*/
typedef void (*on_found_object_cb_t)(void*, LibFibreObject* obj, LibFibreInterface* intf);
/**
* @brief on_lost_object callback type for libfibre_start_discovery().
*/
typedef void (*on_lost_object_cb_t)(void*, LibFibreObject* obj);
typedef void (*on_stopped_cb_t)(void*, LibFibreStatus);
typedef void (*on_attribute_added_cb_t)(void*, LibFibreAttribute*, const char* name, size_t name_length, LibFibreInterface*, const char* intf_name, size_t intf_name_length);
typedef void (*on_attribute_removed_cb_t)(void*, LibFibreAttribute*);
/**
* @brief on_function_added callback type for libfibre_subscribe_to_interface().
*
* @param ctx: The user data that was passed to libfibre_subscribe_to_interface().
* @param func: A handle for the function. Remains valid until the corresponding
* call to on_function_removed().
* @param name: The ASCII-encoded name of the function.
* @param name_length: Length in bytes of the name.
* @param input_names: A null-terminated list of null-terminated ASCII-encoded
* strings. Each string corresponds to the name of one input argument.
* The list and the string buffers are only valid for the duration of the
* callback. They must not be freed by the application.
* @param input_codecs: A null-terminated list of null-terminated ASCII-encoded
* strings. Each string names the codec of one input argument.
* The list and the string buffers are only valid for the duration of the
* callback. They must not be freed by the application.
* @param output_names: Analogous to input_names.
* @param output_codecs: Analogous to output names.
*/
typedef void (*on_function_added_cb_t)(void* ctx, LibFibreFunction* func, const char* name, size_t name_length, const char** input_names, const char** input_codecs, const char** output_names, const char** output_codecs);
typedef void (*on_function_removed_cb_t)(void*, LibFibreFunction*);
/**
* @brief Callback type for libfibre_call().
*
* For an overview of the coroutine call control flow see libfibre_call().
*
* @param ctx: The context pointer that was passed to libfibre_call().
* @param tx_end: End of the range of data that was accepted by libfibre. This
* is always in the interval [tx_buf, tx_buf + tx_len] where `tx_buf` and
* `tx_len` are the arguments of the corresponding libfibre_call() call.
* @param tx_end: End of the range of data that was returned by libfibre. This
* is always in the interval [rx_buf, rx_buf + rx_len] where `rx_buf` and
* `rx_len` are the arguments of the corresponding libfibre_call() call.
* @param tx_buf: The application should set this to the next buffer to
* transmit. The buffer must remain valid until the next callback
* invokation.
* @param tx_len: The length of tx_buf. Must be zero if tx_buf is NULL.
* @param rx_buf: The application should set this to the buffer into which data
* should be written. The buffer must remain allocated until the next
* callback invokation.
* @param rx_len: The length of rx_buf. Must be zero if rx_buf is NULL.
*
* @retval kFibreOk: The application set tx_buf and rx_buf to valid or empty
* buffers and libfibre should invoke the callback again when it has
* made progress.
* @retval kFibreBusy: The application cannot provide a new tx_buf or rx_buf at
* the moment. The application will eventually call libfibre_call() for
* this coroutine call again.
* @retval kFibreClosed: The application may have returned non-empty buffers and
* if libfibre manages to fully handle these buffers it shall consider
* the call ended.
* @retval kFibreCancelled: The application did not set valid tx and rx buffers
* and libfibre should consider the call cancelled. Libfibre will not
* invoke the callback anymore.
*/
typedef LibFibreStatus (*libfibre_call_cb_t)(void* ctx,
LibFibreStatus status,
const unsigned char* tx_end, unsigned char* rx_end,
const unsigned char** tx_buf, size_t* tx_len,
unsigned char** rx_buf, size_t* rx_len);
/**
* @brief TX completion callback type for libfibre_start_tx().
*
* @param ctx: The user data that was passed to libfibre_start_tx().
* @param tx_stream: The TX stream on which the TX operation completed.
* @param status: The status of the last TX operation.
* - kFibreOk: The indicated range of the TX buffer was successfully
* transmitted and the stream might accept more data.
* - kFibreClosed: The indicated range of the TX buffer was successfully
* transmitted and the stream will no longer accept any data.
* - Any other status: Successful transmission of the data cannot be
* guaranteed and no more data can be sent on this stream.
* @param tx_end: Points to the address after the last byte read from the
* TX buffer. This pointer always points to a valid position in the
* buffer (or the end of the buffer), even if the transmission failed.
* However if the status is something other than kFibreOk and
* kFibreClosed then the pointer may not precisely indicate the
* transmitted data range.
*/
typedef void (*on_tx_completed_cb_t)(void* ctx, LibFibreTxStream* tx_stream, LibFibreStatus status, const uint8_t* tx_end);
/**
* @brief RX completion callback type for libfibre_start_rx().
*
* @param ctx: The user data that was passed to libfibre_start_rx().
* @param rx_stream: The RX stream on which the RX operation completed.
* @param status: The status of the last RX operation.
* - kFibreOk: The indicated range of the RX buffer was successfully
* filled with received data and the stream might emit more data.
* - kFibreClosed: The indicated range of the RX buffer was successfully
* filled with received data and the stream will emit no more data.
* - Any other status: Successful transmission of the data cannot be
* guaranteed and no more data can be sent on this stream.
* @param rx_end: Points to the address after the last byte written to the
* RX buffer. This pointer always points to a valid position in the
* buffer (or the end of the buffer), even if the reception failed.
* However if the status is something other than kFibreOk and
* kFibreClosed then the pointer may not precisely indicate the
* received data range.
*/
typedef void (*on_rx_completed_cb_t)(void* ctx, LibFibreRxStream* rx_stream, LibFibreStatus status, uint8_t* rx_end);
/**
* @brief Returns the version of the libfibre library.
*
* The returned struct must not be freed.
*
* The version adheres to Semantic Versioning, that means breaking changes of
* the ABI can be detected by an increment of the major version number (unless
* it's zero).
*
* Even if breaking changes are introduced, we promise to keep this function
* backwards compatible.
*/
FIBRE_PUBLIC const struct LibFibreVersion* libfibre_get_version();
/**
* @brief Opens and initializes a Fibre context.
*
* @param event_loop: The event loop on which libfibre will run. Some function
of the event loop can be left unimplemented (set to NULL) depending on
the platform and the backends used (TODO: elaborate).
The event loop must be single threaded and all calls to libfibre must
happen on the event loop thread.
*/
FIBRE_PUBLIC struct LibFibreCtx* libfibre_open(LibFibreEventLoop event_loop);
/**
* @brief Closes a context that was previously opened with libfibre_open().
*
* This function must not be invoked before all ongoing discovery processes
* are stopped and all channels are closed.
*/
FIBRE_PUBLIC void libfibre_close(struct LibFibreCtx* ctx);
/**
* @brief Registers an external channel provider.
*
* Libfibre starts and stops the discoverer on demand as a result of calls
* to libfibre_start_discovery() and libfibre_stop_discovery().
* This can be used by applications to implement transport providers which are
* not supported natively in libfibre.
*/
FIBRE_PUBLIC void libfibre_register_backend(LibFibreCtx* ctx, const char* name,
size_t name_length, on_start_discovery_cb_t on_start_discovery,
on_stop_discovery_cb_t on_stop_discovery, void* cb_ctx);
/**
* @brief Creates a communication domain from the specified spec string.
*
* @param ctx: The libfibre context that was obtained from libfibre_open().
* @param specs: Pointer to an ASCII string encoding the channel specifications.
* Must remain valid for the life time of the discovery.
* See README of the main Fibre repository for details.
* (https://github.com/samuelsadok/fibre/tree/devel).
* @returns: An opaque handle which can be passed to libfibre_start_discovery().
*/
FIBRE_PUBLIC LibFibreDomain* libfibre_open_domain(LibFibreCtx* ctx,
const char* specs, size_t specs_len);
/**
* @brief Closes a domain that was previously opened with libfibre_open_domain().
*/
FIBRE_PUBLIC void libfibre_close_domain(LibFibreDomain* domain);
/**
* @brief Adds new TX and RX channels to a domain.
*
* The channels can be closed with libfibre_close_tx() and libfibre_close_rx().
*/
FIBRE_PUBLIC void libfibre_add_channels(LibFibreDomain* domain, LibFibreRxStream** tx_channel, LibFibreTxStream** rx_channel, size_t mtu);
/**
* @brief Starts looking for Fibre objects that match the specifications.
*
* @param domain: The domain obtained from libfibre_open_domain() on which to
* discover objects.
* @param on_found_object: Invoked for every matching object that is found.
* The application must expect the same object handle to appear more than
* once.
* libfibre increments the internal reference count of the object before
* this call and decrements it after the corresponding call to
* on_lost_object. When the reference count reaches zero the application
* must no longer use it. The reference count is always non-negative.
* @param on_lost_object: Invoked when an object is lost.
* @param on_stopped: Invoked when the discovery stops for any reason, including
* a corresponding call to libfibre_stop_discovery().
* @param cb_ctx: Arbitrary user data passed to the callbacks.
* @returns: An opaque handle which should be passed to libfibre_stop_discovery().
*/
FIBRE_PUBLIC void libfibre_start_discovery(LibFibreDomain* domain,
LibFibreDiscoveryCtx** handle, on_found_object_cb_t on_found_object,
on_lost_object_cb_t on_lost_object,
on_stopped_cb_t on_stopped, void* cb_ctx);
/**
* @brief Stops an ongoing discovery process that was previously started with
* libfibre_start_discovery().
*
* The discovery is stopped asynchronously. That means it must still be
* considered ongoing until the on_stopped callback which was passed to
* libfibre_start_discovery() is invoked. Once this callback is invoked,
* libfibre_stop_discovery() must no longer be called.
*/
FIBRE_PUBLIC void libfibre_stop_discovery(LibFibreDiscoveryCtx* handle);
/**
* @brief Subscribes to changes on the interface.
*
* All functions and attributes which are already part of the interface by the
* time this function is called are also announced to the subscriber.
*
* @param interface: An interface handle that was obtained in the callback of
* libfibre_start_discovery().
* @param on_attribute_added: Invoked when an attribute is added to the
* interface.
* @param on_attribute_removed: Invoked when an attribute is removed from the
* interface, including when the interface is being torn down. This is
* called exactly once for every call to on_attribute_added().
* @param on_function_added: Invoked when a function is added to the
* interface. The input_names, input_codecs, output_names and
* output_codecs arguments are null terminated lists of null terminated
* strings. The name buffer and the four lists are only valid for the
* duration of the callback and must not be freed by the application.
* The function handle remains valid until the corresponding call to
* on_function_removed().
* @param on_function_removed: Invoked when a function is removed from the
* interface, including when the interface is being torn down. This is
* called exactly once for every call to on_function_added().
* @param cb_ctx: Arbitrary user data passed to the callbacks.
*/
FIBRE_PUBLIC void libfibre_subscribe_to_interface(LibFibreInterface* interface,
on_attribute_added_cb_t on_attribute_added,
on_attribute_removed_cb_t on_attribute_removed,
on_function_added_cb_t on_function_added,
on_function_removed_cb_t on_function_removed,
void* cb_ctx);
/**
* @brief Returns the object that corresponds the the specified attribute of
* another object.
*
* This function runs purely locally and therefore returns a result immediately.
*
* TODO: it might be useful to allow this operation to go through to the remote
* device.
* TODO: Specify whether the returned object handle must be identical for
* repeated calls.
*
* @param parent_obj: An object handle that was obtained in the callback of
* libfibre_start_discovery() or from a previous call to
* libfibre_get_attribute().
* @param attr: An attribute handle that was obtained in the on_attribute_added()
* callback of libfibre_subscribe_to_interface().
* @param child_obj_ptr: If and only if the function succeeds, the variable that
* this argument points to is set to the requested subobject. The returned
* object handle is only guaranteed to remain valid for as long as the
* parent object handle is valid.
* @returns: kFibreOk or kFibreInvalidArgument
*/
FIBRE_PUBLIC LibFibreStatus libfibre_get_attribute(LibFibreObject* parent_obj, LibFibreAttribute* attr, LibFibreObject** child_obj_ptr);
/**
* @brief Starts a remote coroutine call or continues or cancels an ongoing call.
*
* A remote coroutine call can be considered a continuous exchange of the
* following tuples:
*
* Client Application ===== (tx_buf, rx_buf, status) ====> libfibre
* Client Application <==== (tx_end, rx_end, status) ===== libfibre
*
* These tuples are exchanged through the input/output arguments of
* libfibre_call() or libfibre_call()'s callback.
*
* If during an ongoing call either of the two parties is unable to respond
* immediately it responds with kFibreBusy and will thus get the responsibility
* to resume the call when able. kFibreCancelled can be issued by either party
* at any time.
*
* Each party must make progress during every control transfer to the other
* party.
*
* For the application this means for every call to libfibre_call() and every
* return from libfibre_call()'s callback the arguments passed from application
* to libfibre must satisfy at least one of the following:
*
* - The call handle is NULL
* - tx_len is non-zero
* - rx_len is non-zero
* - The status is different from kFibreOk
*
* For libfibre this means every for return from libfibre_call() and every
* call to libfibre_call()'s callback the arguments passed from libfibre to
* application satisfy at least one of the following:
*
* - tx_end is larger than the corresponding tx_buf
* - rx_end is larger than the corresponding rx_buf
* - The status is different from kFibreOk
*
* @param func: A function handle that was obtained in the on_function_added()
* callback of libfibre_subscribe_to_interface().
* @param handle: The variable being pointed to by this argument identifies the
* coroutine call. If the variable is NULL it will be set to a new opaque
* handle. If the variable is not NULL the active function call is
* continued or cancelled (depending on status).
* @param tx_buf: The buffer to transmit. If libfibre_call() returns kFibreBusy
* then this buffer must remain valid until `callback` is invoked.
* Otherwise it can be freed immediately after this call.
* @param tx_len: Length of tx_buf. Must be zero if tx_buf is NULL.
* @param rx_buf: The buffer into which the received data should be written. If
* libfibre_call() returns kFibreBusy then this buffer must remain
* allocated until `callback` is invoked. Otherwise it can be freed
* immediately after this call.
* @param rx_len: Length of rx_buf. Must be zero if rx_buf is NULL.
* @param tx_end: End of the range of data that was accepted by libfibre. This
* is always in the interval [tx_buf, tx_buf + tx_len] unless
* libfibre_call() returns kFibreBusy, in which case this is NULL.
* This value does not give any delivery guarantees.
* @param rx_end: End of the range of data that was returned by libfibre. This
* is always in the interval [rx_buf, rx_buf + rx_len] unless
* libfibre_call() returns kFibreBusy, in which case this is NULL.
* @param callback: Will be invoked eventually if and only if libfibre_call()
* returns kFibreBusy. This callback is never invoked from inside
* libfibre_call().
* @param cb_ctx: An opaque application-defined handle that gets passed to
* `callback`.
*
* @retval kFibreOk: libfibre accepted some or all of the tx_buf or filled some
* or all of the rx_buf with data and can immediately accept more TX
* data or provide more RX data.
* @retval kFibreBusy: libfibre will complete the request asynchronously by
* calling `callback`. If this value is returned, then the application
* must not invoke libfibre_call() on the same call handle again until
* `callback` is invoked except for cancelling the call with a status
* of `kFibreCancelled`.
* @retval kFibreClosed: the remote server completed the call and will not
* accept or return any more data on this call. The application must not
* pass the closed call context handle to libfibre_call() anymore.
* @retval kFibreCancelled: the application's cancellation request was honored
* or the remote server cancelled the call. The application must not
* pass the cancelled call context handle to libfibre_call() anymore.
*/
FIBRE_PUBLIC LibFibreStatus libfibre_call(LibFibreFunction* func, LibFibreCallContext** handle,
LibFibreStatus status,
const unsigned char* tx_buf, size_t tx_len,
unsigned char* rx_buf, size_t rx_len,
const unsigned char** tx_end,
unsigned char** rx_end,
libfibre_call_cb_t callback, void* cb_ctx);
/**
* @brief Starts sending data on the specified TX stream.
*
* The TX operation must be considered in progress until the on_completed
* callback is called. Until then the application must not start another TX
* operation on the same stream. In the meantime the application can call
* libfibre_cancel_tx() at any time to abort the operation.
*
* @param tx_stream: The stream on which to send data.
* @param tx_buf: The buffer to transmit. Must remain valid until the operation
* completes.
* @param tx_len: Length of tx_buf.
* @param on_completed: Called when the operation completes, whether successful
* or not.
* @param ctx: Arbitrary user data passed to the on_completed callback.
*/
FIBRE_PUBLIC void libfibre_start_tx(LibFibreTxStream* tx_stream, const uint8_t* tx_buf, size_t tx_len, on_tx_completed_cb_t on_completed, void* ctx);
/**
* @brief Cancels an ongoing TX operation.
*
* Must only be called if there is actually a TX operation in progress for which
* cancellation has not yet been requested.
* The application must still wait for the on_complete callback to be called
* before the operation can be considered finished. The completion callback may
* be called with kFibreCancelled or any other status.
*
* TODO: specify if streams can be restarted (current doc of on_tx_completed_cb_t implies no)
*
* @param tx_stream: The TX stream on which to cancel the ongoing TX operation.
*/
FIBRE_PUBLIC void libfibre_cancel_tx(LibFibreTxStream* tx_stream);
/**
* @brief Permanently close TX stream.
*
* Must not be called while a transfer is ongoing.
*/
FIBRE_PUBLIC void libfibre_close_tx(LibFibreTxStream* tx_stream, LibFibreStatus status);
/**
* @brief Starts receiving data on the specified RX stream.
*
* The RX operation must be considered in progress until the on_completed
* callback is called. Until then the application must not start another RX
* operation on the same stream. In the meantime the application can call
* libfibre_cancel_rx() at any time to abort the operation.
*
* @param rx_stream: The stream on which to receive data.
* @param rx_buf: The buffer to receive to. Must remain valid until the
* operation completes.
* @param rx_len: Length of rx_buf.
* @param on_completed: Called when the operation completes, whether successful
* or not.
* @param ctx: Arbitrary user data passed to the on_completed callback.
*/
FIBRE_PUBLIC void libfibre_start_rx(LibFibreRxStream* rx_stream, uint8_t* rx_buf, size_t rx_len, on_rx_completed_cb_t on_completed, void* ctx);
/**
* @brief Cancels an ongoing RX operation.
*
* Must only be called if there is actually a RX operation in progress for which
* cancellation has not yet been requested.
* The application must still wait for the on_complete callback to be called
* before the operation can be considered finished. The completion callback may
* be called with kFibreCancelled or any other status.
*
* TODO: specify if streams can be restarted (current doc of on_rx_completed_cb_t implies no)
*
* @param rx_stream: The RX stream on which to cancel the ongoing RX operation.
*/
FIBRE_PUBLIC void libfibre_cancel_rx(LibFibreRxStream* rx_stream);
/**
* @brief Permanently close RX stream.
*
* Must not be called while a transfer is ongoing.
*/
FIBRE_PUBLIC void libfibre_close_rx(LibFibreRxStream* rx_stream, LibFibreStatus status);
#ifdef __cplusplus
}
#endif
#endif // __LIBFIBRE_H
@@ -0,0 +1,127 @@
#ifndef __FIBRE_SIMPLE_SERDES
#define __FIBRE_SIMPLE_SERDES
#include "cpp_utils.hpp"
#include "limits.h"
#include <optional> // TODO: make C++11 backport of this
#include <cstring>
#include <stdint.h>
template<typename T, bool BigEndian, typename = void>
struct SimpleSerializer;
template<typename T>
using LittleEndianSerializer = SimpleSerializer<T, false>;
template<typename T>
using BigEndianSerializer = SimpleSerializer<T, true>;
/* @brief Serializer/deserializer for arbitrary integral number types */
// TODO: allow reading an arbitrary number of bits
template<typename T, bool BigEndian>
struct SimpleSerializer<T, BigEndian, typename std::enable_if_t<std::is_integral<T>::value>> {
static constexpr size_t BIT_WIDTH = std::numeric_limits<T>::digits;
static constexpr size_t BYTE_WIDTH = (BIT_WIDTH + 7) / 8;
template<typename TIterator>
static std::optional<T> read(TIterator* begin, TIterator end = nullptr) {
T result = 0;
if (BigEndian) {
for (size_t i = BYTE_WIDTH; i > 0; (i++, (*begin)++)) {
if (end && !(*begin < end))
return std::nullopt;
uint8_t byte = **begin;
result |= static_cast<T>(byte) << ((i - 1) << 3);
}
} else {
for (size_t i = 0; i < BYTE_WIDTH; (i++, (*begin)++)) {
if (end && !(*begin < end))
return std::nullopt;
uint8_t byte = **begin;
result |= static_cast<T>(byte) << (i << 3);
}
}
return result;
}
template<typename TIterator>
static bool write(T value, TIterator* begin, TIterator end = nullptr) {
if (BigEndian) {
for (size_t i = BYTE_WIDTH; i > 0; (i--, (*begin)++)) {
if (end && !(*begin < end))
return false;
uint8_t byte = static_cast<uint8_t>((value >> ((i - 1) << 3)) & 0xff);
**begin = byte;
}
} else {
for (size_t i = 0; i < BYTE_WIDTH; (i++, (*begin)++)) {
if (end && !(*begin < end))
return false;
uint8_t byte = static_cast<uint8_t>((value >> (i << 3)) & 0xff);
**begin = byte;
}
}
return true;
}
};
template<typename T>
inline std::optional<T> read_le(fibre::cbufptr_t* buffer) {
static_assert(is_complete<LittleEndianSerializer<T>>(), "no LittleEndianSerializer is defined for type T");
return LittleEndianSerializer<T>::read(&buffer->begin(), buffer->end());
}
template<typename T>
inline bool write_le(T value, fibre::bufptr_t* buffer) {
static_assert(is_complete<LittleEndianSerializer<T>>(), "no LittleEndianSerializer is defined for type T");
return LittleEndianSerializer<T>::write(value, &buffer->begin(), buffer->end());
}
template<typename T, typename = typename std::enable_if_t<!std::is_const<T>::value>>
inline size_t write_le(T value, uint8_t* buffer){
//TODO: add static_assert that this is still a little endian machine
std::memcpy(&buffer[0], &value, sizeof(value));
return sizeof(value);
}
template<typename T>
typename std::enable_if_t<std::is_const<T>::value, size_t>
write_le(T value, uint8_t* buffer) {
return write_le<std::remove_const_t<T>>(value, buffer);
}
template<>
inline size_t write_le<float>(float value, uint8_t* buffer) {
static_assert(CHAR_BIT * sizeof(float) == 32, "32 bit floating point expected");
static_assert(std::numeric_limits<float>::is_iec559, "IEEE 754 floating point expected");
uint32_t value_as_uint32;
std::memcpy(&value_as_uint32, &value, sizeof(uint32_t));
return write_le<uint32_t>(value_as_uint32, buffer);
}
template<typename T>
inline size_t read_le(T* value, const uint8_t* buffer){
// TODO: add static_assert that this is still a little endian machine
std::memcpy(value, buffer, sizeof(*value));
return sizeof(*value);
}
template<>
inline size_t read_le<float>(float* value, const uint8_t* buffer) {
static_assert(CHAR_BIT * sizeof(float) == 32, "32 bit floating point expected");
static_assert(std::numeric_limits<float>::is_iec559, "IEEE 754 floating point expected");
return read_le(reinterpret_cast<uint32_t*>(value), buffer);
}
// @brief Reads a value of type T from the buffer.
// @param buffer Pointer to the buffer to be read. The pointer is updated by the number of bytes that were read.
// @param length The number of available bytes in buffer. This value is updated to subtract the bytes that were read.
template<typename T>
static inline T read_le(const uint8_t** buffer, size_t* length) {
T result;
size_t cnt = read_le(&result, *buffer);
*buffer += cnt;
*length -= cnt;
return result;
}
#endif
@@ -0,0 +1,20 @@
#ifndef __FIBRE_STATUS_HPP
#define __FIBRE_STATUS_HPP
namespace fibre {
enum Status {
kFibreOk,
kFibreBusy, //<! The request will complete asynchronously
kFibreCancelled, //!< The operation was cancelled due to a request by the application or the remote peer
kFibreClosed, //!< The operation has finished orderly or shall be finished orderly
kFibreInvalidArgument, //!< Bug in the application
kFibreInternalError, //!< Bug in the local fibre implementation
kFibreProtocolError, //!< A remote peer is misbehaving (indicates bug in the remote peer)
kFibreHostUnreachable, //!< The remote peer can no longer be reached
//kFibreInsufficientData, // maybe we will introduce this to tell the caller that the granularity of the data is too small
};
}
#endif // __FIBRE_STATUS_HPP