*
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
/*[# This is the original template, thus the warning below does not apply to this file #]
|
||||
* ============================ WARNING ============================
|
||||
* ==== This is an autogenerated file. ====
|
||||
* ==== Any changes to this file will be lost when recompiling. ====
|
||||
* =================================================================
|
||||
*
|
||||
* This file contains the toplevel handler for Fibre v0.1 endpoint operations.
|
||||
*
|
||||
* This endpoint-oriented approach will be deprecated in Fibre v0.2 in favor of
|
||||
* a function-oriented approach and a more powerful object model.
|
||||
*
|
||||
*/
|
||||
#ifndef __FIBRE_INTERFACES_HPP
|
||||
#define __FIBRE_INTERFACES_HPP
|
||||
|
||||
#include <fibre/introspection.hpp>
|
||||
|
||||
// Note: with -Og the functions with large switch statements reserves a huge amount
|
||||
// of stack space because they reserves separate space for the stack frame of each
|
||||
// of the inlined functions.
|
||||
// The minimum known set of flags to prevent this is `-O1 -fipa-sra`.
|
||||
// `-O2`, `-O3` and `-Os` are supersets of this.
|
||||
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("s")
|
||||
|
||||
namespace fibre {
|
||||
|
||||
const unsigned char embedded_json[] = [[embedded_endpoint_definitions | to_c_string]];
|
||||
const size_t embedded_json_length = sizeof(embedded_json) - 1;
|
||||
const uint16_t json_crc_ = calc_crc16<CANONICAL_CRC16_POLYNOMIAL>(PROTOCOL_VERSION, embedded_json, embedded_json_length);
|
||||
const uint32_t json_version_id_ = (json_crc_ << 16) | calc_crc16<CANONICAL_CRC16_POLYNOMIAL>(json_crc_, embedded_json, embedded_json_length);
|
||||
|
||||
static void get_property(Introspectable& result, size_t idx) {
|
||||
switch (idx) {
|
||||
[%- for endpoint in endpoints %]
|
||||
[%- if endpoint.function.name == 'exchange' and endpoint.in_bindings | list == ['obj'] %]
|
||||
case [[endpoint.id]]: { [[(endpoint.in_bindings['obj'] + '$') | replace(')$', ', &result.storage_)')]]; result.type_info_ = &FibrePropertyTypeInfo<[[endpoint.function.in['obj'].type.c_name]]>::singleton; } break;
|
||||
[%- endif %]
|
||||
[%- endfor %]
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool endpoint_handler(int idx, cbufptr_t* input_buffer, bufptr_t* output_buffer) {
|
||||
//Introspectable property = get_property(idx);
|
||||
//if property.is_valid()
|
||||
|
||||
switch (idx) {
|
||||
[%- for endpoint in endpoints %]
|
||||
[%- if (endpoint.function.name == 'exchange' or endpoint.function.name == 'read') and endpoint.in_bindings | list == ['obj'] %]
|
||||
case [[endpoint.id]]: { return [[endpoint.function.fullname | to_snake_case]]([% for k, arg in endpoint.function.in.items() %][% if k in endpoint.in_bindings %]static_cast<[[arg.type.c_name]]>([[endpoint.in_bindings[k]]])[% else %]std::nullopt[% endif %], [% endfor %][% for k, arg in endpoint.function.out.items() %][% if k in endpoint.out_bindings %]static_cast<[[arg.type.c_name]]*>([[endpoint.out_bindings[k]]])[% else %]nullptr[% endif %], [% endfor %]input_buffer, output_buffer); } break;
|
||||
[%- else %]
|
||||
case [[endpoint.id]]: { return [[endpoint.function.fullname | to_snake_case]]([% for k, arg in endpoint.function.in.items() %][% if k in endpoint.in_bindings %]static_cast<[[arg.type.c_name]]>([[endpoint.in_bindings[k]]])[% else %]std::nullopt[% endif %], [% endfor %][% for k, arg in endpoint.function.out.items() %][% if k in endpoint.out_bindings %]static_cast<[[arg.type.c_name]]*>([[endpoint.out_bindings[k]]])[% else %]nullptr[% endif %], [% endfor %]input_buffer, output_buffer); } break;
|
||||
[%- endif %]
|
||||
[%- endfor %]
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_endpoint_ref_valid(endpoint_ref_t endpoint_ref) {
|
||||
if (endpoint_ref.json_crc != json_crc_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (endpoint_ref.endpoint_id) {
|
||||
[%- for endpoint in endpoints %]
|
||||
case [[endpoint.id]]: return true;
|
||||
[%- endfor %]
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool set_endpoint_from_float(endpoint_ref_t endpoint_ref, float value) {
|
||||
if (endpoint_ref.json_crc != json_crc_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Introspectable property{};
|
||||
get_property(property, endpoint_ref.endpoint_id);
|
||||
const FloatSettableTypeInfo* type_info = dynamic_cast<const FloatSettableTypeInfo*>(property.get_type_info());
|
||||
return type_info && type_info->set_float(property, value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#pragma GCC pop_options
|
||||
|
||||
#endif // __FIBRE_INTERFACES_HPP
|
||||
@@ -0,0 +1,40 @@
|
||||
/*[# This is the original template, thus the warning below does not apply to this file #]
|
||||
* ============================ WARNING ============================
|
||||
* ==== This is an autogenerated file. ====
|
||||
* ==== Any changes to this file will be lost when recompiling. ====
|
||||
* =================================================================
|
||||
*
|
||||
* This file contains serializing/deserializing stubs for the functions defined
|
||||
* in your interface file.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <fibre/bufptr.hpp>
|
||||
|
||||
[% for intf in interfaces.values() %]
|
||||
[% for func in intf.functions.values() %]
|
||||
static inline bool [[func.fullname | to_snake_case]]([% for arg in func.in.values() %]std::optional<[[arg.type.c_name]]> in_[[arg.name]], [% endfor %][% for arg in func.out.values() %][[arg.type.c_name]]* out_[[arg.name]], [% endfor %]fibre::cbufptr_t* input_buffer, fibre::bufptr_t* output_buffer) {
|
||||
[%- if func.in %]
|
||||
bool success = [% for arg in func.in.values() %](in_[[arg.name]].has_value() || (in_[[arg.name]] = fibre::Codec<[[arg.type.c_name]]>::decode(input_buffer)).has_value()[% if arg.optional %] || true[% endif %])[% if not loop.last %]
|
||||
&& [% endif %][% endfor %];
|
||||
[%- else %]
|
||||
bool success = true;
|
||||
[%- endif %]
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
[%- if func.implementation %]
|
||||
[% if func.out %]std::tuple<[% for arg in func.out.values() %][[arg.type.c_name]][[', ' if not loop.last]][% endfor %]> ret = [% endif %][[func.implementation]]([% for arg in func.in.values() %]in_[[arg.name]][% if not arg.optional %].value()[% endif %][[', ' if not loop.last]][% endfor %]);
|
||||
[%- else %]
|
||||
[% if func.out %]std::tuple<[% for arg in func.out.values() %][[arg.type.c_name]][[', ' if not loop.last]][% endfor %]> ret = [% endif %]in_[[(func.in.values() | first).name]].value()->[[func.name]]([% for arg in func.in.values() | skip_first %]in_[[arg.name]][% if not arg.optional %].value()[% endif %][[', ' if not loop.last]][% endfor %]);
|
||||
[%- endif %]
|
||||
[%- if func.out %]
|
||||
return [% for arg in func.out.values() %]((out_[[arg.name]] && ((*out_[[arg.name]] = std::get<[[loop.index0]]>(ret)), true)) || fibre::Codec<[[arg.type.c_name]]>::encode(std::get<[[loop.index0]]>(ret), output_buffer))[% if not loop.last %]
|
||||
&& [% endif %][% endfor %];
|
||||
[%- else %]
|
||||
return true;
|
||||
[%- endif %]
|
||||
}
|
||||
[% endfor %]
|
||||
[% endfor %]
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
#ifndef __FIBRE_BUFPTR_HPP
|
||||
#define __FIBRE_BUFPTR_HPP
|
||||
|
||||
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(const std::vector<std::remove_const_t<T>>& vector)
|
||||
: generic_bufptr_t(vector.data(), vector.size()) {}
|
||||
|
||||
generic_bufptr_t(const generic_bufptr_t<std::remove_const_t<T>>& 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); }
|
||||
|
||||
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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,56 @@
|
||||
#ifndef __CRC_HPP
|
||||
#define __CRC_HPP
|
||||
|
||||
#include <stdint.h>
|
||||
#include <limits.h>
|
||||
|
||||
// Calculates an arbitrary CRC for one byte.
|
||||
// Adapted from https://barrgroup.com/Embedded-Systems/How-To/CRC-Calculation-C-Code
|
||||
template<typename T, unsigned POLYNOMIAL>
|
||||
static T calc_crc(T remainder, uint8_t value) {
|
||||
constexpr T BIT_WIDTH = (CHAR_BIT * sizeof(T));
|
||||
constexpr T TOPBIT = ((T)1 << (BIT_WIDTH - 1));
|
||||
|
||||
// Bring the next byte into the remainder.
|
||||
remainder ^= (value << (BIT_WIDTH - 8));
|
||||
|
||||
// Perform modulo-2 division, a bit at a time.
|
||||
for (uint8_t bit = 8; bit; --bit) {
|
||||
if (remainder & TOPBIT) {
|
||||
remainder = (remainder << 1) ^ POLYNOMIAL;
|
||||
} else {
|
||||
remainder = (remainder << 1);
|
||||
}
|
||||
}
|
||||
|
||||
return remainder;
|
||||
}
|
||||
|
||||
template<typename T, unsigned POLYNOMIAL>
|
||||
static T calc_crc(T remainder, const uint8_t* buffer, size_t length) {
|
||||
while (length--)
|
||||
remainder = calc_crc<T, POLYNOMIAL>(remainder, *(buffer++));
|
||||
return remainder;
|
||||
}
|
||||
|
||||
template<unsigned POLYNOMIAL>
|
||||
static uint8_t calc_crc8(uint8_t remainder, uint8_t value) {
|
||||
return calc_crc<uint8_t, POLYNOMIAL>(remainder, value);
|
||||
}
|
||||
|
||||
template<unsigned POLYNOMIAL>
|
||||
static uint16_t calc_crc16(uint16_t remainder, uint8_t value) {
|
||||
return calc_crc<uint16_t, POLYNOMIAL>(remainder, value);
|
||||
}
|
||||
|
||||
template<unsigned POLYNOMIAL>
|
||||
static uint8_t calc_crc8(uint8_t remainder, const uint8_t* buffer, size_t length) {
|
||||
return calc_crc<uint8_t, POLYNOMIAL>(remainder, buffer, length);
|
||||
}
|
||||
|
||||
template<unsigned POLYNOMIAL>
|
||||
static uint16_t calc_crc16(uint16_t remainder, const uint8_t* buffer, size_t length) {
|
||||
return calc_crc<uint16_t, POLYNOMIAL>(remainder, buffer, length);
|
||||
}
|
||||
|
||||
#endif /* __CRC_HPP */
|
||||
@@ -0,0 +1,336 @@
|
||||
|
||||
#ifndef __DECODERS_HPP
|
||||
#define __DECODERS_HPP
|
||||
|
||||
#include "protocol.hpp"
|
||||
#include "crc.hpp"
|
||||
#include "cpp_utils.hpp"
|
||||
#include <utility>
|
||||
|
||||
|
||||
/* Base classes --------------------------------------------------------------*/
|
||||
|
||||
// @brief Base class for stream based decoders.
|
||||
// A stream based decoder is a decoder that processes arbitrary length data blocks.
|
||||
class StreamDecoder : public StreamSink {
|
||||
public:
|
||||
// @brief Returns 0 if no error ocurred, otherwise a non-zero error code.
|
||||
// Once process_bytes returned an error, subsequent calls to get_status must return the same error.
|
||||
// If the decoder is in an error state, the behavior of get_expected_bytes and process_bytes is undefined.
|
||||
virtual int get_status() = 0;
|
||||
|
||||
// @brief Returns the minimum number of bytes that are still needed to complete this decoder.
|
||||
// If 0, the decoder is considered complete and any subsequent call to process_bytes must process
|
||||
// exactly 0 bytes.
|
||||
// process_bytes() must always process all provided bytes unless the decoder expects no more bytes
|
||||
// afterwards
|
||||
virtual size_t get_expected_bytes() = 0;
|
||||
};
|
||||
|
||||
// @brief Base class for a decoder that is fed in a block-wise fashion.
|
||||
// This base class is provided for convenience when implementing certain types of decoders.
|
||||
// A StreamDecoder can be obtained from a BlockDecoder by using StreamDecoder_from_BlockDecoder.
|
||||
template<unsigned BLOCKSIZE>
|
||||
class BlockDecoder {
|
||||
public:
|
||||
typedef std::integral_constant<size_t, BLOCKSIZE> block_size;
|
||||
|
||||
virtual int get_status() = 0;
|
||||
virtual size_t get_expected_blocks() = 0;
|
||||
virtual int process_block(const uint8_t block[BLOCKSIZE]) = 0;
|
||||
private:
|
||||
};
|
||||
|
||||
// @brief Base class for a decoder that is fed in a byte-wise fashion
|
||||
// This base class is provided for convenience when implementing certain types of decoders.
|
||||
// A StreamDecoder can be obtained from a ByteDecoder by using StreamDecoder_from_ByteDecoder.
|
||||
class ByteDecoder {
|
||||
public:
|
||||
virtual int get_status() = 0;
|
||||
virtual size_t get_expected_bytes() = 0;
|
||||
virtual int process_byte(uint8_t byte) = 0;
|
||||
};
|
||||
|
||||
/* Converter classes ---------------------------------------------------------*/
|
||||
|
||||
// @brief Encapsulates a BlockDecoder to make it look like a StreamDecoder
|
||||
// @tparam T The encapsulated BlockDecoder type.
|
||||
// Must inherit from BlockDecoder.
|
||||
template<typename T, ENABLE_IF(TypeChecker<T>::template all_are<BlockDecoder<T::block_size::value>>())>
|
||||
class StreamDecoder_from_BlockDecoder : public StreamDecoder {
|
||||
public:
|
||||
// @brief Imitates the constructor signature of the encapsulated type.
|
||||
template<typename ... Args, ENABLE_IF(TypeChecker<Args...>::template first_is_not<StreamDecoder_from_BlockDecoder>())>
|
||||
explicit StreamDecoder_from_BlockDecoder(Args&& ... args)
|
||||
: block_decoder_(std::forward<Args>(args)...) {
|
||||
EXPECT_TYPE(T, BlockDecoder<T::block_size::value>);
|
||||
}
|
||||
|
||||
inline int get_status() final {
|
||||
return block_decoder_.get_status();
|
||||
}
|
||||
|
||||
inline size_t get_expected_bytes() final {
|
||||
size_t expected_bytes = block_decoder_.get_expected_blocks() * T::block_size::value;
|
||||
return expected_bytes - std::min(expected_bytes, buffer_pos_);
|
||||
}
|
||||
|
||||
inline int process_bytes(const uint8_t* buffer, size_t length, size_t* processed_bytes) final {
|
||||
while (!get_status() && get_expected_bytes() && length) {
|
||||
// use the incoming bytes to fill internal buffer to get a complete block
|
||||
size_t n_copy = std::min(length, T::block_size::value - buffer_pos_);
|
||||
memcpy(buffer_ + buffer_pos_, buffer, n_copy);
|
||||
buffer += n_copy;
|
||||
length -= n_copy;
|
||||
if (processed_bytes) (*processed_bytes) += n_copy;
|
||||
buffer_pos_ += n_copy;
|
||||
|
||||
// if we have a full block, process it
|
||||
if (buffer_pos_ == T::block_size::value) {
|
||||
block_decoder_.process_block(buffer_);
|
||||
buffer_pos_ = 0;
|
||||
}
|
||||
}
|
||||
return get_status();
|
||||
}
|
||||
|
||||
size_t get_free_space() { return SIZE_MAX; } // TODO: deprecate
|
||||
private:
|
||||
T block_decoder_;
|
||||
size_t buffer_pos_ = 0;
|
||||
uint8_t buffer_[T::block_size::value];
|
||||
};
|
||||
|
||||
// @brief Encapsulates a ByteDecoder to make it look like a BlockDecoder
|
||||
// @tparam T The encapsulated ByteDecoder type.
|
||||
// Must inherit from ByteDecoder.
|
||||
template<typename T, ENABLE_IF(TypeChecker<T>::template all_are<ByteDecoder>())>
|
||||
class BlockDecoder_from_ByteDecoder : public BlockDecoder<1> {
|
||||
public:
|
||||
// @brief Imitates the constructor signature of the encapsulated type.
|
||||
template<typename ... Args, ENABLE_IF(TypeChecker<Args...>::template first_is_not<BlockDecoder_from_ByteDecoder>())>
|
||||
BlockDecoder_from_ByteDecoder(Args&& ... args)
|
||||
: byte_decoder_(std::forward<Args>(args)...) {
|
||||
EXPECT_TYPE(T, ByteDecoder);
|
||||
}
|
||||
|
||||
inline int get_status() final {
|
||||
return byte_decoder_.get_status();
|
||||
}
|
||||
inline size_t get_expected_blocks() final {
|
||||
return byte_decoder_.get_expected_bytes();
|
||||
}
|
||||
inline int process_block(const uint8_t block[1]) final {
|
||||
int status = byte_decoder_.process_byte(*block);
|
||||
return status;
|
||||
}
|
||||
private:
|
||||
T byte_decoder_;
|
||||
};
|
||||
|
||||
// @brief Encapsulates a ByteDecoder to make it look like a StreamDecoder
|
||||
// @tparam T The encapsulated ByteDecoder type.
|
||||
// Must inherit from ByteDecoder.
|
||||
template<typename T, ENABLE_IF(TypeChecker<T>::template all_are<ByteDecoder>())>
|
||||
class StreamDecoder_from_ByteDecoder : public StreamDecoder {
|
||||
public:
|
||||
// @brief Imitates the constructor signature of the encapsulated type.
|
||||
template<typename ... Args, ENABLE_IF(TypeChecker<Args...>::template first_is_not<StreamDecoder_from_ByteDecoder>())>
|
||||
StreamDecoder_from_ByteDecoder(Args&& ... args)
|
||||
: byte_decoder_(std::forward<Args>(args)...) {
|
||||
EXPECT_TYPE(T, ByteDecoder);
|
||||
}
|
||||
|
||||
inline int get_status() final {
|
||||
return byte_decoder_.get_status();
|
||||
}
|
||||
inline size_t get_expected_bytes() final {
|
||||
return byte_decoder_.get_expected_bytes();
|
||||
}
|
||||
inline size_t get_free_space() { return SIZE_MAX; }
|
||||
inline int process_bytes(const uint8_t* buffer, size_t length, size_t* processed_bytes) final {
|
||||
while (!byte_decoder_.get_status() && byte_decoder_.get_expected_bytes() && length) {
|
||||
length--;
|
||||
if (processed_bytes) (*processed_bytes)++;
|
||||
byte_decoder_.process_byte(*(buffer++));
|
||||
}
|
||||
return byte_decoder_.get_status();
|
||||
}
|
||||
private:
|
||||
T byte_decoder_;
|
||||
};
|
||||
|
||||
/* Decoder implementations ---------------------------------------------------*/
|
||||
|
||||
template<typename T>
|
||||
class VarintByteDecoder : public ByteDecoder {
|
||||
public:
|
||||
static constexpr T BIT_WIDTH = (CHAR_BIT * sizeof(T));
|
||||
|
||||
VarintByteDecoder(T& state_variable) :
|
||||
state_variable_(state_variable)
|
||||
{
|
||||
}
|
||||
|
||||
size_t get_expected_bytes() final {
|
||||
return done_ ? 0 : 1;
|
||||
}
|
||||
|
||||
int get_status() final {
|
||||
return status_;
|
||||
}
|
||||
|
||||
int process_byte(uint8_t input_byte) final {
|
||||
if (bit_pos_ == 0) {
|
||||
LOG_FIBRE("start decoding varint, with 0x%02x => %zx\n", input_byte, (uintptr_t)&state_variable_);
|
||||
state_variable_ = 0;
|
||||
}
|
||||
LOG_FIBRE("varint: decode %02x << %zu at %zx\n", input_byte, bit_pos_, &bit_pos_);
|
||||
// we assume bit_pos_ < BIT_WIDTH
|
||||
state_variable_ |= (static_cast<T>(input_byte & 0x7f) << bit_pos_);
|
||||
if (((state_variable_ >> bit_pos_) & 0x7f) != static_cast<T>(input_byte & 0x7f)) {
|
||||
LOG_FIBRE("varint overflow: tried to add %02x << %zu\n", input_byte, bit_pos_);
|
||||
return (status_ = -1); // overflow
|
||||
}
|
||||
bit_pos_ += 7;
|
||||
done_ = !(input_byte & 0x80);
|
||||
return (status_ = (done_ || bit_pos_ < BIT_WIDTH) ? 0 : -1);
|
||||
}
|
||||
|
||||
private:
|
||||
T& state_variable_;
|
||||
// At all times where status_ != 0 the following statement holds:
|
||||
// (done_ || bit_pos_ < BIT_WIDTH)
|
||||
//size_t bit_pos_ = 0; // bit position
|
||||
size_t bit_pos_ = 0; // bit position
|
||||
int status_ = 0;
|
||||
bool done_ = false;
|
||||
int data[1024] = {0};
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
using VarintStreamDecoder = StreamDecoder_from_ByteDecoder<VarintByteDecoder<T>>;
|
||||
|
||||
// This double nested type should work identically but makes it way harder for the compiler to optimize
|
||||
//template<typename T>
|
||||
//using VarintBlockDecoder = BlockDecoder_from_ByteDecoder<VarintByteDecoder<T>>;
|
||||
//template<typename T>
|
||||
//using VarintStreamDecoder = StreamDecoder_from_BlockDecoder<VarintBlockDecoder<T>>;
|
||||
|
||||
template<typename T>
|
||||
inline VarintStreamDecoder<T> make_varint_decoder(T& variable) {
|
||||
return VarintStreamDecoder<T>(variable);
|
||||
}
|
||||
|
||||
inline VarintStreamDecoder<GET_TYPE_OF(&ReceiverState::endpoint_id)> make_endpoint_id_decoder(ReceiverState& state) {
|
||||
return make_varint_decoder(state.endpoint_id);
|
||||
}
|
||||
inline VarintStreamDecoder<GET_TYPE_OF(&ReceiverState::length)> make_length_decoder(ReceiverState& state) {
|
||||
return make_varint_decoder(state.length);
|
||||
}
|
||||
|
||||
|
||||
|
||||
template<uint8_t INIT, uint8_t POLYNOMIAL, typename TDecoder,
|
||||
ENABLE_IF(TypeChecker<TDecoder>::template all_are<StreamDecoder>())>
|
||||
class CRC8BlockDecoder : public BlockDecoder<CRC8_BLOCKSIZE> {
|
||||
public:
|
||||
CRC8BlockDecoder(TDecoder&& inner_decoder) :
|
||||
inner_decoder_(std::forward<TDecoder>(inner_decoder)) {
|
||||
}
|
||||
|
||||
int get_status() final {
|
||||
return status_;
|
||||
}
|
||||
|
||||
size_t get_expected_blocks() final {
|
||||
return (inner_decoder_.get_expected_bytes() + CRC8_BLOCKSIZE - 2) / (CRC8_BLOCKSIZE - 1);
|
||||
}
|
||||
|
||||
int process_block(const uint8_t input_block[4]) final {
|
||||
current_crc_ = calc_crc8<POLYNOMIAL>(current_crc_, input_block, CRC8_BLOCKSIZE - 1);
|
||||
if (current_crc_ != input_block[CRC8_BLOCKSIZE - 1])
|
||||
return status_ = -1;
|
||||
return status_ = inner_decoder_.process_bytes(input_block, CRC8_BLOCKSIZE - 1, nullptr);
|
||||
}
|
||||
private:
|
||||
TDecoder inner_decoder_;
|
||||
int status_ = 0;
|
||||
uint8_t current_crc_ = INIT;
|
||||
};
|
||||
|
||||
template<unsigned INIT, unsigned POLYNOMIAL, typename TDecoder>
|
||||
using CRC8StreamDecoder = StreamDecoder_from_BlockDecoder<CRC8BlockDecoder<INIT, POLYNOMIAL, TDecoder>>;
|
||||
|
||||
template<unsigned INIT, unsigned POLYNOMIAL, typename TDecoder>
|
||||
inline CRC8StreamDecoder<INIT, POLYNOMIAL, TDecoder> make_crc8_decoder(TDecoder&& decoder) {
|
||||
return CRC8StreamDecoder<INIT, POLYNOMIAL, TDecoder>(std::forward<TDecoder>(decoder));
|
||||
}
|
||||
|
||||
// TODO: ENABLE_IF(TypeChecker<TDecoders...>::template all_are<StreamDecoder>())
|
||||
template<typename ... TDecoders>
|
||||
class DecoderChain;
|
||||
|
||||
template<>
|
||||
class DecoderChain<> : public StreamDecoder {
|
||||
public:
|
||||
size_t get_expected_bytes() { return 0; }
|
||||
int get_status() { return 0; }
|
||||
int process_bytes(const uint8_t *input, size_t length, size_t* processed_bytes) { return 0; }
|
||||
size_t get_free_space() { return SIZE_MAX; } // TODO: deprecate
|
||||
};
|
||||
|
||||
template<typename TDecoder, typename ... TDecoders>
|
||||
class DecoderChain<TDecoder, TDecoders...> : public StreamDecoder {
|
||||
public:
|
||||
DecoderChain(TDecoder&& this_decoder, TDecoders&& ... subsequent_decoders) :
|
||||
this_decoder_(std::forward<TDecoder>(this_decoder)),
|
||||
subsequent_decoders_(std::forward<TDecoders>(subsequent_decoders)...)
|
||||
{
|
||||
EXPECT_TYPE(TDecoder, StreamDecoder);
|
||||
}
|
||||
|
||||
int get_status() final {
|
||||
// If this decoder or any of the subsequent decoders failed, return error code.
|
||||
int this_status = this_decoder_.get_status();
|
||||
int subsequent_status = subsequent_decoders_.get_status();
|
||||
if (this_status)
|
||||
return this_status;
|
||||
else if (subsequent_status)
|
||||
return subsequent_status;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t get_expected_bytes() final {
|
||||
return this_decoder_.get_expected_bytes() + subsequent_decoders_.get_expected_bytes();
|
||||
}
|
||||
|
||||
int process_bytes(const uint8_t *input, size_t length, size_t* processed_bytes) final {
|
||||
if (this_decoder_.get_expected_bytes()) {
|
||||
LOG_FIBRE("decoder chain: process %zu bytes in segment %s\n", length, typeid(TDecoder).name());
|
||||
size_t chunk = 0;
|
||||
int status = this_decoder_.process_bytes(input, length, &chunk);
|
||||
input += chunk;
|
||||
length -= chunk;
|
||||
if (processed_bytes) (*processed_bytes) += chunk;
|
||||
if (status)
|
||||
return status;
|
||||
if (!length)
|
||||
return 0;
|
||||
}
|
||||
return subsequent_decoders_.process_bytes(input, length, processed_bytes);
|
||||
}
|
||||
|
||||
size_t get_free_space() { return SIZE_MAX; } // TODO: deprecate
|
||||
private:
|
||||
TDecoder this_decoder_;
|
||||
DecoderChain<TDecoders...> subsequent_decoders_;
|
||||
};
|
||||
|
||||
template<typename ... TDecoders>
|
||||
inline DecoderChain<TDecoders...> make_decoder_chain(TDecoders&& ... decoders) {
|
||||
return DecoderChain<TDecoders...>(std::forward<TDecoders>(decoders)...);
|
||||
}
|
||||
|
||||
#endif // __DECODERS_HPP
|
||||
@@ -0,0 +1,323 @@
|
||||
|
||||
#ifndef __ENCODERS_HPP
|
||||
#define __ENCODERS_HPP
|
||||
|
||||
#include "protocol.hpp"
|
||||
#include "crc.hpp"
|
||||
#include "cpp_utils.hpp"
|
||||
#include <utility>
|
||||
|
||||
struct Request {
|
||||
endpoint_id_t endpoint_id;
|
||||
size_t length;
|
||||
};
|
||||
|
||||
/* Base classes --------------------------------------------------------------*/
|
||||
|
||||
// @brief Base class for all stream encoders
|
||||
// A stream based encoder is an encoder that generates arbitrary length data blocks.
|
||||
class StreamEncoder : public StreamSource {
|
||||
public:
|
||||
// @brief Returns 0 if no error ocurred, otherwise a non-zero error code.
|
||||
// Once get_bytes returned an error, subsequent calls to get_status must return the same error.
|
||||
// If the encoder is in an error state, the behavior of get_available_bytes and get_bytes is undefined.
|
||||
virtual int get_status() = 0;
|
||||
|
||||
// @brief Returns the minimum number of bytes that will still be generated by this encoder.
|
||||
// If 0, the encoder is considered complete and any subsequent call to get_bytes must generate
|
||||
// exactly 0 bytes.
|
||||
// get_bytes() must always generate as many bytes as requested unless the encoder generates no more bytes
|
||||
// afterwards
|
||||
virtual size_t get_available_bytes() = 0;
|
||||
};
|
||||
|
||||
// @brief Base class for an encoder that is fed in a block-wise fashion.
|
||||
// This base class is provided for convenience when implementing certain types of encoders.
|
||||
// A StreamEncoder can be obtained from a BlockEncoder by using StreamEncoder_from_BlockEncoder.
|
||||
template<unsigned BLOCKSIZE>
|
||||
class BlockEncoder {
|
||||
public:
|
||||
typedef std::integral_constant<size_t, BLOCKSIZE> block_size;
|
||||
|
||||
virtual int get_status() = 0;
|
||||
virtual size_t get_available_blocks() = 0;
|
||||
virtual int get_block(uint8_t block[BLOCKSIZE]) = 0;
|
||||
private:
|
||||
};
|
||||
|
||||
// @brief Base class for an encoder that is fed in a byte-wise fashion
|
||||
// This base class is provided for convenience when implementing certain types of encoders.
|
||||
// A StreamEncoder can be obtained from a ByteEncoder by using StreamEncoder_from_ByteEncoder.
|
||||
class ByteEncoder {
|
||||
public:
|
||||
virtual int get_status() = 0;
|
||||
virtual size_t get_available_bytes() = 0;
|
||||
virtual int get_byte(uint8_t *output_byte) = 0;
|
||||
};
|
||||
|
||||
/* Converter classes ---------------------------------------------------------*/
|
||||
|
||||
// @brief Encapsulates a BlockEncoder to make it look like a StreamEncoder
|
||||
// @tparam T The encapsulated BlockEncoder type.
|
||||
// Must inherit from to BlockEncoder.
|
||||
template<typename T, ENABLE_IF(TypeChecker<T>::template all_are<BlockEncoder<T::block_size::value>>())>
|
||||
class StreamEncoder_from_BlockEncoder : public StreamEncoder {
|
||||
public:
|
||||
// @brief Imitates the constructor signature of the encapsulated type.
|
||||
template<typename ... Args, ENABLE_IF(TypeChecker<Args...>::template first_is_not<StreamEncoder_from_BlockEncoder>())>
|
||||
explicit StreamEncoder_from_BlockEncoder(Args&& ... args)
|
||||
: block_encoder_(std::forward<Args>(args)...) {
|
||||
EXPECT_TYPE(T, BlockEncoder<T::block_size::value>);
|
||||
}
|
||||
|
||||
inline int get_status() final {
|
||||
return buffered_bytes_ ? 0 : block_encoder_.get_status();
|
||||
}
|
||||
|
||||
inline size_t get_available_bytes() final {
|
||||
size_t available_bytes = block_encoder_.get_available_blocks() * T::block_size::value;
|
||||
return available_bytes + buffered_bytes_;
|
||||
}
|
||||
|
||||
inline int get_bytes(uint8_t* buffer, size_t length, size_t* generated_bytes) final {
|
||||
while (!get_status() && get_available_bytes() && length) {
|
||||
// if the buffer is empty, retrieve a new block from the encode
|
||||
if (!buffered_bytes_) {
|
||||
block_encoder_.get_block(buffer_);
|
||||
buffered_bytes_ = T::block_size::value;
|
||||
}
|
||||
|
||||
// hand the buffered bytes to the encoder
|
||||
size_t n_copy = std::min(buffered_bytes_, length);
|
||||
memcpy(buffer, buffer_ + T::block_size::value - n_copy, n_copy);
|
||||
length -= n_copy;
|
||||
buffer += n_copy;
|
||||
if (generated_bytes) (*generated_bytes) += n_copy;
|
||||
buffered_bytes_ -= n_copy;
|
||||
}
|
||||
return get_status();
|
||||
}
|
||||
private:
|
||||
T block_encoder_;
|
||||
size_t buffered_bytes_ = 0;
|
||||
uint8_t buffer_[T::block_size::value];
|
||||
};
|
||||
|
||||
// @brief Encapsulates a ByteEncoder to make it look like a BlockEncoder
|
||||
// @tparam T The encapsulated ByteEncoder type.
|
||||
// Must inherit from ByteEncoder.
|
||||
template<typename T, ENABLE_IF(TypeChecker<T>::template all_are<ByteEncoder>())>
|
||||
class BlockEncoder_from_ByteEncoder : public BlockEncoder<1> {
|
||||
public:
|
||||
// @brief Imitates the constructor signature of the encapsulated type.
|
||||
template<typename ... Args, ENABLE_IF(TypeChecker<Args...>::template first_is_not<BlockEncoder_from_ByteEncoder>())>
|
||||
BlockEncoder_from_ByteEncoder(Args&& ... args)
|
||||
: byte_encoder_(std::forward<Args>(args)...) {
|
||||
EXPECT_TYPE(T, ByteEncoder);
|
||||
}
|
||||
|
||||
inline int get_status() final {
|
||||
return byte_encoder_.get_status();
|
||||
}
|
||||
inline size_t get_available_blocks() final {
|
||||
return byte_encoder_.get_available_bytes();
|
||||
}
|
||||
inline int get_block(uint8_t block[1]) final {
|
||||
int status = byte_encoder_.get_byte(*block);
|
||||
return status;
|
||||
}
|
||||
private:
|
||||
T byte_encoder_;
|
||||
};
|
||||
|
||||
// @brief Encapsulates a ByteEncoder to make it look like a StreamEncoder
|
||||
// @tparam T The encapsulated ByteEncoder type.
|
||||
// Must inherit from ByteEncoder.
|
||||
template<typename T, ENABLE_IF(TypeChecker<T>::template all_are<ByteEncoder>())>
|
||||
class StreamEncoder_from_ByteEncoder : public StreamEncoder {
|
||||
public:
|
||||
// @brief Imitates the constructor signature of the encapsulated type.
|
||||
template<typename ... Args, ENABLE_IF(TypeChecker<Args...>::template first_is_not<StreamEncoder_from_ByteEncoder>())>
|
||||
StreamEncoder_from_ByteEncoder(Args&& ... args)
|
||||
: byte_encoder_(std::forward<Args>(args)...) {
|
||||
EXPECT_TYPE(T, ByteEncoder);
|
||||
}
|
||||
|
||||
inline int get_status() final {
|
||||
return byte_encoder_.get_status();
|
||||
}
|
||||
inline size_t get_available_bytes() final {
|
||||
return byte_encoder_.get_available_bytes();
|
||||
}
|
||||
inline int get_bytes(uint8_t* buffer, size_t length, size_t* generated_bytes) final {
|
||||
while (!byte_encoder_.get_status() && byte_encoder_.get_available_bytes() && length) {
|
||||
length--;
|
||||
if (generated_bytes) (*generated_bytes)++;
|
||||
byte_encoder_.get_byte(buffer++);
|
||||
}
|
||||
return byte_encoder_.get_status();
|
||||
}
|
||||
private:
|
||||
T byte_encoder_;
|
||||
};
|
||||
|
||||
/* Encoder implementations ---------------------------------------------------*/
|
||||
|
||||
template<typename T>
|
||||
class VarintByteEncoder : public ByteEncoder {
|
||||
public:
|
||||
static constexpr T BIT_WIDTH = (CHAR_BIT * sizeof(T));
|
||||
|
||||
VarintByteEncoder(const T& state_variable) :
|
||||
state_variable_(state_variable)
|
||||
{}
|
||||
|
||||
size_t get_available_bytes() final {
|
||||
return done_ ? 0 : 1;
|
||||
}
|
||||
|
||||
int get_status() final {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int get_byte(uint8_t *output_byte) final {
|
||||
if (bit_pos_ == 0)
|
||||
LOG_FIBRE("start encoding varint, from pos %d\n", bit_pos_);
|
||||
*output_byte = (state_variable_ >> bit_pos_) & 0x7f;
|
||||
bit_pos_ += 7;
|
||||
if (bit_pos_ < BIT_WIDTH && (state_variable_ >> bit_pos_)) {
|
||||
LOG_FIBRE("remainder: %x\n", state_variable_ >> bit_pos_);
|
||||
*output_byte |= 0x80;
|
||||
}else
|
||||
done_ = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
const T& state_variable_;
|
||||
size_t bit_pos_ = 0; // bit position
|
||||
int status_ = 0;
|
||||
bool done_ = false;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
using VarintStreamEncoder = StreamEncoder_from_ByteEncoder<VarintByteEncoder<T>>;
|
||||
|
||||
template<typename T>
|
||||
VarintStreamEncoder<T> make_varint_encoder(const T& variable) {
|
||||
return VarintStreamEncoder<T>(variable);
|
||||
}
|
||||
|
||||
VarintStreamEncoder<GET_TYPE_OF(&Request::endpoint_id)> make_endpoint_id_encoder(const Request& request) {
|
||||
return make_varint_encoder(request.endpoint_id);
|
||||
}
|
||||
VarintStreamEncoder<GET_TYPE_OF(&Request::length)> make_length_encoder(const Request& request) {
|
||||
return make_varint_encoder(request.length);
|
||||
}
|
||||
|
||||
template<uint8_t INIT, uint8_t POLYNOMIAL, typename TEncoder,
|
||||
ENABLE_IF(TypeChecker<TEncoder>::template all_are<StreamEncoder>())>
|
||||
class CRC8BlockEncoder : public BlockEncoder<CRC8_BLOCKSIZE> {
|
||||
public:
|
||||
CRC8BlockEncoder(TEncoder&& inner_encoder)
|
||||
: inner_encoder_(std::forward<TEncoder>(inner_encoder)) {}
|
||||
|
||||
int get_status() final {
|
||||
return status_;
|
||||
}
|
||||
|
||||
size_t get_available_blocks() final {
|
||||
return (inner_encoder_.get_available_bytes() + CRC8_BLOCKSIZE - 2) / (CRC8_BLOCKSIZE - 1);
|
||||
}
|
||||
|
||||
int get_block(uint8_t block[4]) final {
|
||||
size_t generated_bytes = 0;
|
||||
status_ = inner_encoder_.get_bytes(block, CRC8_BLOCKSIZE - 1, &generated_bytes);
|
||||
if (status_)
|
||||
return status_;
|
||||
|
||||
// zero out unused end of the block
|
||||
while (generated_bytes < CRC8_BLOCKSIZE)
|
||||
block[generated_bytes++] = 0;
|
||||
|
||||
block[CRC8_BLOCKSIZE - 1] = current_crc_ = calc_crc8<POLYNOMIAL>(current_crc_, block, CRC8_BLOCKSIZE - 1);
|
||||
return 0;
|
||||
}
|
||||
private:
|
||||
TEncoder inner_encoder_;
|
||||
int status_ = 0;
|
||||
uint8_t current_crc_ = INIT;
|
||||
};
|
||||
|
||||
template<unsigned INIT, unsigned POLYNOMIAL, typename TEncoder>
|
||||
using CRC8StreamEncoder = StreamEncoder_from_BlockEncoder<CRC8BlockEncoder<INIT, POLYNOMIAL, TEncoder>>;
|
||||
|
||||
template<unsigned INIT, unsigned POLYNOMIAL, typename TEncoder>
|
||||
CRC8StreamEncoder<INIT, POLYNOMIAL, TEncoder> make_crc8_encoder(TEncoder&& encoder) {
|
||||
return CRC8StreamEncoder<INIT, POLYNOMIAL, TEncoder>(std::forward<TEncoder>(encoder));
|
||||
}
|
||||
|
||||
template<typename ... TEncoders>
|
||||
class EncoderChain;
|
||||
|
||||
template<>
|
||||
class EncoderChain<> : public StreamEncoder {
|
||||
public:
|
||||
size_t get_available_bytes() final { return 0; }
|
||||
int get_status() final { return 0; }
|
||||
int get_bytes(uint8_t *output, size_t length, size_t* generated_bytes) final { return 0; }
|
||||
};
|
||||
|
||||
template<typename TEncoder, typename ... TEncoders>
|
||||
class EncoderChain<TEncoder, TEncoders...> : public StreamEncoder {
|
||||
public:
|
||||
EncoderChain(TEncoder&& this_encoder, TEncoders&& ... subsequent_encoders) :
|
||||
this_encoder_(std::forward<TEncoder>(this_encoder)),
|
||||
subsequent_encoders_(std::forward<TEncoders>(subsequent_encoders)...)
|
||||
{
|
||||
EXPECT_TYPE(TEncoder, StreamEncoder);
|
||||
}
|
||||
|
||||
size_t get_available_bytes() final {
|
||||
return this_encoder_.get_available_bytes() + subsequent_encoders_.get_available_bytes();
|
||||
}
|
||||
|
||||
int get_status() final {
|
||||
// If this encoder or any of the subsequent encoders failed, return error code.
|
||||
int this_status = this_encoder_.get_status();
|
||||
int subsequent_status = subsequent_encoders_.get_status();
|
||||
if (this_status)
|
||||
return this_status;
|
||||
else if (subsequent_status)
|
||||
return subsequent_status;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
int get_bytes(uint8_t *output, size_t length, size_t* generated_bytes) final {
|
||||
if (this_encoder_.get_available_bytes()) {
|
||||
LOG_FIBRE("encoder chain: generate %zu bytes in segment %s\n", length, typeid(TEncoder).name());
|
||||
size_t chunk = 0;
|
||||
int status = this_encoder_.get_bytes(output, length, &chunk);
|
||||
if (status)
|
||||
return status;
|
||||
output += chunk;
|
||||
length -= chunk;
|
||||
if (generated_bytes) *generated_bytes += chunk;
|
||||
if (!length)
|
||||
return 0;
|
||||
}
|
||||
return subsequent_encoders_.get_bytes(output, length, generated_bytes);
|
||||
}
|
||||
|
||||
private:
|
||||
TEncoder this_encoder_;
|
||||
EncoderChain<TEncoders...> subsequent_encoders_;
|
||||
};
|
||||
|
||||
template<typename ... TEncoders>
|
||||
EncoderChain<TEncoders...> make_encoder_chain(TEncoders&& ... encoders) {
|
||||
return EncoderChain<TEncoders...>(std::forward<TEncoders>(encoders)...);
|
||||
}
|
||||
|
||||
#endif // __ENCODERS_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<16, 4>::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_));
|
||||
return *(T*)&obj.storage_;
|
||||
}
|
||||
template<typename T> const T& TypeInfo::as(const Introspectable& obj) {
|
||||
static_assert(sizeof(T) <= sizeof(obj.storage_));
|
||||
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,4 @@
|
||||
|
||||
#include "protocol.hpp"
|
||||
|
||||
int serve_on_tcp(unsigned int port);
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
#include "protocol.hpp"
|
||||
|
||||
int serve_on_udp(unsigned int port);
|
||||
@@ -0,0 +1,621 @@
|
||||
/*
|
||||
see protocol.md for the protocol specification
|
||||
*/
|
||||
|
||||
#ifndef __PROTOCOL_HPP
|
||||
#define __PROTOCOL_HPP
|
||||
|
||||
// TODO: resolve assert
|
||||
#define assert(expr)
|
||||
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <cmath>
|
||||
//#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <cstring>
|
||||
#include "crc.hpp"
|
||||
#include "cpp_utils.hpp"
|
||||
#include "bufptr.hpp"
|
||||
#include "simple_serdes.hpp"
|
||||
|
||||
// Note that this option cannot be used to debug UART because it prints on UART
|
||||
//#define DEBUG_FIBRE
|
||||
#ifdef DEBUG_FIBRE
|
||||
#define LOG_FIBRE(...) do { printf(__VA_ARGS__); } while (0)
|
||||
#else
|
||||
#define LOG_FIBRE(...) ((void) 0)
|
||||
#endif
|
||||
|
||||
|
||||
// Default CRC-8 Polynomial: x^8 + x^5 + x^4 + x^2 + x + 1
|
||||
// Can protect a 4 byte payload against toggling of up to 5 bits
|
||||
// source: https://users.ece.cmu.edu/~koopman/crc/index.html
|
||||
constexpr uint8_t CANONICAL_CRC8_POLYNOMIAL = 0x37;
|
||||
constexpr uint8_t CANONICAL_CRC8_INIT = 0x42;
|
||||
|
||||
constexpr size_t CRC8_BLOCKSIZE = 4;
|
||||
|
||||
// Default CRC-16 Polynomial: 0x9eb2 x^16 + x^13 + x^12 + x^11 + x^10 + x^8 + x^6 + x^5 + x^2 + 1
|
||||
// Can protect a 135 byte payload against toggling of up to 5 bits
|
||||
// source: https://users.ece.cmu.edu/~koopman/crc/index.html
|
||||
// Also known as CRC-16-DNP
|
||||
constexpr uint16_t CANONICAL_CRC16_POLYNOMIAL = 0x3d65;
|
||||
constexpr uint16_t CANONICAL_CRC16_INIT = 0x1337;
|
||||
|
||||
constexpr uint8_t CANONICAL_PREFIX = 0xAA;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* move to fibre_config.h ******************************/
|
||||
|
||||
typedef size_t endpoint_id_t;
|
||||
|
||||
struct ReceiverState {
|
||||
endpoint_id_t endpoint_id;
|
||||
size_t length;
|
||||
uint16_t seqno_thread;
|
||||
uint16_t seqno;
|
||||
bool expect_ack;
|
||||
bool expect_response;
|
||||
bool enforce_ordering;
|
||||
};
|
||||
|
||||
/*******************************************************/
|
||||
|
||||
|
||||
constexpr uint16_t PROTOCOL_VERSION = 1;
|
||||
|
||||
// This value must not be larger than USB_TX_DATA_SIZE defined in usbd_cdc_if.h
|
||||
constexpr uint16_t TX_BUF_SIZE = 32; // does not work with 64 for some reason
|
||||
constexpr uint16_t RX_BUF_SIZE = 128; // larger values than 128 have currently no effect because of protocol limitations
|
||||
|
||||
// Maximum time we allocate for processing and responding to a request
|
||||
constexpr uint32_t PROTOCOL_SERVER_TIMEOUT_MS = 10;
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint16_t json_crc = 0;
|
||||
uint16_t endpoint_id = 0;
|
||||
} endpoint_ref_t;
|
||||
|
||||
|
||||
namespace fibre {
|
||||
// These symbols are defined in the autogenerated endpoints.hpp
|
||||
extern const unsigned char embedded_json[];
|
||||
extern const size_t embedded_json_length;
|
||||
extern const uint16_t json_crc_;
|
||||
extern const uint32_t json_version_id_;
|
||||
bool endpoint_handler(int idx, cbufptr_t* input_buffer, bufptr_t* output_buffer);
|
||||
bool endpoint0_handler(cbufptr_t* input_buffer, bufptr_t* output_buffer);
|
||||
bool is_endpoint_ref_valid(endpoint_ref_t endpoint_ref);
|
||||
bool set_endpoint_from_float(endpoint_ref_t endpoint_ref, float value);
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
class PacketSink {
|
||||
public:
|
||||
// @brief Get the maximum packet length (aka maximum transmission unit)
|
||||
// A packet size shall take no action and return an error code if the
|
||||
// caller attempts to send an oversized packet.
|
||||
//virtual size_t get_mtu() = 0;
|
||||
|
||||
// @brief Processes a packet.
|
||||
// The blocking behavior shall depend on the thread-local deadline_ms variable.
|
||||
// @return: 0 on success, otherwise a non-zero error code
|
||||
// TODO: define what happens when the packet is larger than what the implementation can handle.
|
||||
virtual int process_packet(const uint8_t* buffer, size_t length) = 0;
|
||||
};
|
||||
|
||||
class StreamSink {
|
||||
public:
|
||||
// @brief Processes a chunk of bytes that is part of a continuous stream.
|
||||
// The blocking behavior shall depend on the thread-local deadline_ms variable.
|
||||
// @param processed_bytes: if not NULL, shall be incremented by the number of
|
||||
// bytes that were consumed.
|
||||
// @return: 0 on success, otherwise a non-zero error code
|
||||
virtual int process_bytes(const uint8_t* buffer, size_t length, size_t* processed_bytes) = 0;
|
||||
|
||||
// @brief Returns the number of bytes that can still be written to the stream.
|
||||
// Shall return SIZE_MAX if the stream has unlimited lenght.
|
||||
// TODO: deprecate
|
||||
virtual size_t get_free_space() = 0;
|
||||
|
||||
/*int process_bytes(const uint8_t* buffer, size_t length) {
|
||||
size_t processed_bytes = 0;
|
||||
return process_bytes(buffer, length, &processed_bytes);
|
||||
}*/
|
||||
};
|
||||
|
||||
class StreamSource {
|
||||
public:
|
||||
// @brief Generate a chunk of bytes that are part of a continuous stream.
|
||||
// The blocking behavior shall depend on the thread-local deadline_ms variable.
|
||||
// @param generated_bytes: if not NULL, shall be incremented by the number of
|
||||
// bytes that were written to buffer.
|
||||
// @return: 0 on success, otherwise a non-zero error code
|
||||
virtual int get_bytes(uint8_t* buffer, size_t length, size_t* generated_bytes) = 0;
|
||||
|
||||
// @brief Returns the number of bytes that can still be written to the stream.
|
||||
// Shall return SIZE_MAX if the stream has unlimited lenght.
|
||||
// TODO: deprecate
|
||||
//virtual size_t get_free_space() = 0;
|
||||
};
|
||||
|
||||
class StreamToPacketSegmenter : public StreamSink {
|
||||
public:
|
||||
explicit StreamToPacketSegmenter(PacketSink& output) :
|
||||
output_(output)
|
||||
{
|
||||
};
|
||||
|
||||
int process_bytes(const uint8_t *buffer, size_t length, size_t* processed_bytes) override;
|
||||
|
||||
size_t get_free_space() { return SIZE_MAX; }
|
||||
|
||||
private:
|
||||
uint8_t header_buffer_[3] = {0};
|
||||
size_t header_index_ = 0;
|
||||
uint8_t packet_buffer_[RX_BUF_SIZE] = {0};
|
||||
size_t packet_index_ = 0;
|
||||
size_t packet_length_ = 0;
|
||||
PacketSink& output_;
|
||||
};
|
||||
|
||||
|
||||
class StreamBasedPacketSink : public PacketSink {
|
||||
public:
|
||||
explicit StreamBasedPacketSink(StreamSink& output) :
|
||||
output_(output)
|
||||
{
|
||||
};
|
||||
|
||||
//size_t get_mtu() { return SIZE_MAX; }
|
||||
int process_packet(const uint8_t *buffer, size_t length) override;
|
||||
|
||||
private:
|
||||
StreamSink& output_;
|
||||
};
|
||||
|
||||
// @brief: Represents a stream sink that's based on an underlying packet sink.
|
||||
// A single call to process_bytes may result in multiple packets being sent.
|
||||
class PacketBasedStreamSink : public StreamSink {
|
||||
public:
|
||||
explicit PacketBasedStreamSink(PacketSink& packet_sink) : _packet_sink(packet_sink) {}
|
||||
~PacketBasedStreamSink() {}
|
||||
|
||||
int process_bytes(const uint8_t* buffer, size_t length, size_t* processed_bytes) override {
|
||||
// Loop to ensure all bytes get sent
|
||||
while (length) {
|
||||
size_t chunk = length;
|
||||
// send chunk as packet
|
||||
if (_packet_sink.process_packet(buffer, chunk))
|
||||
return -1;
|
||||
buffer += chunk;
|
||||
length -= chunk;
|
||||
if (processed_bytes)
|
||||
*processed_bytes += chunk;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t get_free_space() { return SIZE_MAX; }
|
||||
|
||||
private:
|
||||
PacketSink& _packet_sink;
|
||||
};
|
||||
|
||||
// Implements the StreamSink interface by writing into a fixed size
|
||||
// memory buffer.
|
||||
class MemoryStreamSink : public StreamSink {
|
||||
public:
|
||||
MemoryStreamSink(uint8_t *buffer, size_t length) :
|
||||
buffer_(buffer),
|
||||
buffer_length_(length) {}
|
||||
|
||||
// Returns 0 on success and -1 if the buffer could not accept everything because it became full
|
||||
int process_bytes(const uint8_t* buffer, size_t length, size_t* processed_bytes) override {
|
||||
size_t chunk = length < buffer_length_ ? length : buffer_length_;
|
||||
memcpy(buffer_, buffer, chunk);
|
||||
buffer_ += chunk;
|
||||
buffer_length_ -= chunk;
|
||||
if (processed_bytes)
|
||||
*processed_bytes += chunk;
|
||||
return chunk == length ? 0 : -1;
|
||||
}
|
||||
|
||||
size_t get_free_space() { return buffer_length_; }
|
||||
|
||||
private:
|
||||
uint8_t * buffer_;
|
||||
size_t buffer_length_;
|
||||
};
|
||||
|
||||
// Implements the StreamSink interface by discarding the first couple of bytes
|
||||
// and then forwarding the rest to another stream.
|
||||
class NullStreamSink : public StreamSink {
|
||||
public:
|
||||
NullStreamSink(size_t skip, StreamSink& follow_up_stream) :
|
||||
skip_(skip),
|
||||
follow_up_stream_(follow_up_stream) {}
|
||||
|
||||
// Returns 0 on success and -1 if the buffer could not accept everything because it became full
|
||||
int process_bytes(const uint8_t* buffer, size_t length, size_t* processed_bytes) override {
|
||||
if (skip_ < length) {
|
||||
buffer += skip_;
|
||||
length -= skip_;
|
||||
if (processed_bytes)
|
||||
*processed_bytes += skip_;
|
||||
skip_ = 0;
|
||||
return follow_up_stream_.process_bytes(buffer, length, processed_bytes);
|
||||
} else {
|
||||
skip_ -= length;
|
||||
if (processed_bytes)
|
||||
*processed_bytes += length;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
size_t get_free_space() override { return skip_ + follow_up_stream_.get_free_space(); }
|
||||
|
||||
private:
|
||||
size_t skip_;
|
||||
StreamSink& follow_up_stream_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Implements the StreamSink interface by calculating the CRC16 checksum
|
||||
// on the data that is sent to it.
|
||||
class CRC16Calculator : public StreamSink {
|
||||
public:
|
||||
explicit CRC16Calculator(uint16_t crc16_init) :
|
||||
crc16_(crc16_init) {}
|
||||
|
||||
int process_bytes(const uint8_t* buffer, size_t length, size_t* processed_bytes) override{
|
||||
crc16_ = calc_crc16<CANONICAL_CRC16_POLYNOMIAL>(crc16_, buffer, length);
|
||||
if (processed_bytes)
|
||||
*processed_bytes += length;
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t get_free_space() override { return SIZE_MAX; }
|
||||
|
||||
uint16_t get_crc16() { return crc16_; }
|
||||
private:
|
||||
uint16_t crc16_;
|
||||
};
|
||||
|
||||
|
||||
namespace fibre {
|
||||
template<typename T, typename = void>
|
||||
struct Codec {
|
||||
static std::optional<T> decode(cbufptr_t* buffer) { return std::nullopt; }
|
||||
};
|
||||
|
||||
template<> struct Codec<bool> {
|
||||
static std::optional<bool> decode(cbufptr_t* buffer) { return (buffer->begin() == buffer->end()) ? std::nullopt : std::make_optional((bool)*(buffer->begin()++)); }
|
||||
static bool encode(bool value, bufptr_t* buffer) { return SimpleSerializer<uint8_t, false>::write(value, &(buffer->begin()), buffer->end()); }
|
||||
};
|
||||
template<> struct Codec<int8_t> {
|
||||
static std::optional<int8_t> decode(cbufptr_t* buffer) { return SimpleSerializer<int8_t, false>::read(&(buffer->begin()), buffer->end()); }
|
||||
static bool encode(int8_t value, bufptr_t* buffer) { return SimpleSerializer<int8_t, false>::write(value, &(buffer->begin()), buffer->end()); }
|
||||
};
|
||||
template<> struct Codec<uint8_t> {
|
||||
static std::optional<uint8_t> decode(cbufptr_t* buffer) { return SimpleSerializer<uint8_t, false>::read(&(buffer->begin()), buffer->end()); }
|
||||
static bool encode(uint8_t value, bufptr_t* buffer) { return SimpleSerializer<uint8_t, false>::write(value, &(buffer->begin()), buffer->end()); }
|
||||
};
|
||||
template<> struct Codec<int16_t> {
|
||||
static std::optional<int16_t> decode(cbufptr_t* buffer) { return SimpleSerializer<int16_t, false>::read(&(buffer->begin()), buffer->end()); }
|
||||
static bool encode(int16_t value, bufptr_t* buffer) { return SimpleSerializer<int16_t, false>::write(value, &(buffer->begin()), buffer->end()); }
|
||||
};
|
||||
template<> struct Codec<uint16_t> {
|
||||
static std::optional<uint16_t> decode(cbufptr_t* buffer) { return SimpleSerializer<uint16_t, false>::read(&(buffer->begin()), buffer->end()); }
|
||||
static bool encode(uint16_t value, bufptr_t* buffer) { return SimpleSerializer<uint16_t, false>::write(value, &(buffer->begin()), buffer->end()); }
|
||||
};
|
||||
template<> struct Codec<int32_t> {
|
||||
static std::optional<int32_t> decode(cbufptr_t* buffer) { return SimpleSerializer<int32_t, false>::read(&(buffer->begin()), buffer->end()); }
|
||||
static bool encode(int32_t value, bufptr_t* buffer) { return SimpleSerializer<int32_t, false>::write(value, &(buffer->begin()), buffer->end()); }
|
||||
};
|
||||
template<> struct Codec<uint32_t> {
|
||||
static std::optional<uint32_t> decode(cbufptr_t* buffer) { return SimpleSerializer<uint32_t, false>::read(&(buffer->begin()), buffer->end()); }
|
||||
static bool encode(uint32_t value, bufptr_t* buffer) { return SimpleSerializer<uint32_t, false>::write(value, &(buffer->begin()), buffer->end()); }
|
||||
};
|
||||
template<> struct Codec<int64_t> {
|
||||
static std::optional<int64_t> decode(cbufptr_t* buffer) { return SimpleSerializer<int64_t, false>::read(&(buffer->begin()), buffer->end()); }
|
||||
static bool encode(int64_t value, bufptr_t* buffer) { return SimpleSerializer<int64_t, false>::write(value, &(buffer->begin()), buffer->end()); }
|
||||
};
|
||||
template<> struct Codec<uint64_t> {
|
||||
static std::optional<uint64_t> decode(cbufptr_t* buffer) { return SimpleSerializer<uint64_t, false>::read(&(buffer->begin()), buffer->end()); }
|
||||
static bool encode(uint64_t value, bufptr_t* buffer) { return SimpleSerializer<uint64_t, false>::write(value, &(buffer->begin()), buffer->end()); }
|
||||
};
|
||||
template<> struct Codec<float> {
|
||||
static std::optional<float> decode(cbufptr_t* buffer) {
|
||||
std::optional<uint32_t> int_val = Codec<uint32_t>::decode(buffer);
|
||||
return int_val.has_value() ? std::optional<float>(*reinterpret_cast<float*>(&int_val.value())) : std::nullopt;
|
||||
}
|
||||
static bool encode(float value, bufptr_t* buffer) {
|
||||
void* ptr = &value;
|
||||
return Codec<uint32_t>::encode(*reinterpret_cast<uint32_t*>(ptr), buffer);
|
||||
}
|
||||
};
|
||||
template<typename T>
|
||||
struct Codec<T, std::enable_if_t<std::is_enum<T>::value>> {
|
||||
static std::optional<T> decode(cbufptr_t* buffer) {
|
||||
std::optional<int32_t> int_val = SimpleSerializer<int32_t, false>::read(&(buffer->begin()), buffer->end());
|
||||
return int_val.has_value() ? std::make_optional(static_cast<T>(int_val.value())) : std::nullopt;
|
||||
}
|
||||
static bool encode(T value, bufptr_t* buffer) { return SimpleSerializer<int32_t, false>::write(value, &(buffer->begin()), buffer->end()); }
|
||||
};
|
||||
template<> struct Codec<endpoint_ref_t> {
|
||||
static std::optional<endpoint_ref_t> decode(cbufptr_t* buffer) {
|
||||
std::optional<uint16_t> val0 = SimpleSerializer<uint16_t, false>::read(&(buffer->begin()), buffer->end());
|
||||
std::optional<uint16_t> val1 = SimpleSerializer<uint16_t, false>::read(&(buffer->begin()), buffer->end());
|
||||
return (val0.has_value() && val1.has_value()) ? std::make_optional(endpoint_ref_t{val1.value(), val0.value()}) : std::nullopt;
|
||||
}
|
||||
static bool encode(endpoint_ref_t value, bufptr_t* buffer) {
|
||||
return SimpleSerializer<uint16_t, false>::write(value.endpoint_id, &(buffer->begin()), buffer->end())
|
||||
&& SimpleSerializer<uint16_t, false>::write(value.json_crc, &(buffer->begin()), buffer->end());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/* @brief Handles the communication protocol on one channel.
|
||||
*
|
||||
* When instantiated with a list of endpoints and an output packet sink,
|
||||
* objects of this class will handle packets passed into process_packet,
|
||||
* pass the relevant data to the corresponding endpoints and dispatch response
|
||||
* packets on the output.
|
||||
*/
|
||||
class BidirectionalPacketBasedChannel : public PacketSink {
|
||||
public:
|
||||
explicit BidirectionalPacketBasedChannel(PacketSink& output) :
|
||||
output_(output)
|
||||
{ }
|
||||
|
||||
//size_t get_mtu() {
|
||||
// return SIZE_MAX;
|
||||
//}
|
||||
int process_packet(const uint8_t* buffer, size_t length) override;
|
||||
private:
|
||||
PacketSink& output_;
|
||||
uint8_t tx_buf_[TX_BUF_SIZE] = {0};
|
||||
};
|
||||
|
||||
|
||||
/* ToString / FromString functions -------------------------------------------*/
|
||||
/*
|
||||
* These functions are currently not used by Fibre and only here to
|
||||
* support the ODrive ASCII protocol.
|
||||
* TODO: find a general way for client code to augment endpoints with custom
|
||||
* functions
|
||||
*/
|
||||
|
||||
template<typename T>
|
||||
struct format_traits_t;
|
||||
|
||||
// template<> struct format_traits_t<float> { using type = void;
|
||||
// static constexpr const char * fmt = "%f";
|
||||
// static constexpr const char * fmtp = "%f";
|
||||
// };
|
||||
template<> struct format_traits_t<int64_t> { using type = void;
|
||||
static constexpr const char * fmt = "%lld";
|
||||
static constexpr const char * fmtp = "%lld";
|
||||
};
|
||||
template<> struct format_traits_t<uint64_t> { using type = void;
|
||||
static constexpr const char * fmt = "%llu";
|
||||
static constexpr const char * fmtp = "%llu";
|
||||
};
|
||||
template<> struct format_traits_t<int32_t> { using type = void;
|
||||
static constexpr const char * fmt = "%ld";
|
||||
static constexpr const char * fmtp = "%ld";
|
||||
};
|
||||
template<> struct format_traits_t<uint32_t> { using type = void;
|
||||
static constexpr const char * fmt = "%lu";
|
||||
static constexpr const char * fmtp = "%lu";
|
||||
};
|
||||
// TODO: change all overloads to fundamental int type space
|
||||
template<> struct format_traits_t<unsigned int> { using type = void;
|
||||
static constexpr const char * fmt = "%ud";
|
||||
static constexpr const char * fmtp = "%ud";
|
||||
};
|
||||
template<> struct format_traits_t<int16_t> { using type = void;
|
||||
static constexpr const char * fmt = "%hd";
|
||||
static constexpr const char * fmtp = "%hd";
|
||||
};
|
||||
template<> struct format_traits_t<uint16_t> { using type = void;
|
||||
static constexpr const char * fmt = "%hu";
|
||||
static constexpr const char * fmtp = "%hu";
|
||||
};
|
||||
template<> struct format_traits_t<int8_t> { using type = void;
|
||||
static constexpr const char * fmt = "%hhd";
|
||||
static constexpr const char * fmtp = "%d";
|
||||
};
|
||||
template<> struct format_traits_t<uint8_t> { using type = void;
|
||||
static constexpr const char * fmt = "%hhu";
|
||||
static constexpr const char * fmtp = "%u";
|
||||
};
|
||||
|
||||
template<typename T, typename = typename format_traits_t<T>::type>
|
||||
static bool to_string(const T& value, char * buffer, size_t length, int) {
|
||||
snprintf(buffer, length, format_traits_t<T>::fmtp, value);
|
||||
return true;
|
||||
}
|
||||
// Special case for float because printf promotes float to double, and we get warnings
|
||||
template<typename T = float>
|
||||
static bool to_string(const float& value, char * buffer, size_t length, int) {
|
||||
snprintf(buffer, length, "%f", (double)value);
|
||||
return true;
|
||||
}
|
||||
template<typename T = bool>
|
||||
static bool to_string(const bool& value, char * buffer, size_t length, int) {
|
||||
buffer[0] = value ? '1' : '0';
|
||||
buffer[1] = 0;
|
||||
return true;
|
||||
}
|
||||
template<typename T>
|
||||
static bool to_string(const T& value, char * buffer, size_t length, ...) {
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename T, typename = typename format_traits_t<T>::type>
|
||||
static bool from_string(const char * buffer, size_t length, T* property, int) {
|
||||
// Note for T == uint8_t: Even though we supposedly use the correct format
|
||||
// string sscanf treats our pointer as pointer-to-int instead of
|
||||
// pointer-to-uint8_t. To avoid an unexpected memory access we first read
|
||||
// into a union.
|
||||
union { T t; int i; } val;
|
||||
if (sscanf(buffer, format_traits_t<T>::fmt, &val.t) == 1) {
|
||||
*property = val.t;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Special case for float because printf promotes float to double, and we get warnings
|
||||
template<typename T = float>
|
||||
static bool from_string(const char * buffer, size_t length, float* property, int) {
|
||||
return sscanf(buffer, "%f", property) == 1;
|
||||
}
|
||||
template<typename T = bool>
|
||||
static bool from_string(const char * buffer, size_t length, bool* property, int) {
|
||||
int val;
|
||||
if (sscanf(buffer, "%d", &val) != 1)
|
||||
return false;
|
||||
*property = val;
|
||||
return true;
|
||||
}
|
||||
template<typename T>
|
||||
static bool from_string(const char * buffer, size_t length, T* property, ...) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//template<typename T, typename = typename std>
|
||||
//bool set_from_float_ex(float value, T* property) {
|
||||
// return false;
|
||||
//}
|
||||
|
||||
namespace conversion {
|
||||
//template<typename T>
|
||||
template<typename T>
|
||||
bool set_from_float_ex(float value, float* property, int) {
|
||||
return *property = value, true;
|
||||
}
|
||||
template<typename T>
|
||||
bool set_from_float_ex(float value, bool* property, int) {
|
||||
return *property = (value >= 0.0f), true;
|
||||
}
|
||||
template<typename T, typename = std::enable_if_t<std::is_integral<T>::value && !std::is_const<T>::value>>
|
||||
bool set_from_float_ex(float value, T* property, int) {
|
||||
return *property = static_cast<T>(std::round(value)), true;
|
||||
}
|
||||
template<typename T>
|
||||
bool set_from_float_ex(float value, T* property, ...) {
|
||||
return false;
|
||||
}
|
||||
template<typename T>
|
||||
bool set_from_float(float value, T* property) {
|
||||
return set_from_float_ex<T>(value, property, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
struct Property {
|
||||
Property(void* ctx, T(*getter)(void*), void(*setter)(void*, T))
|
||||
: ctx_(ctx), getter_(getter), setter_(setter) {}
|
||||
Property(T* ctx)
|
||||
: ctx_(ctx), getter_([](void* ctx){ return *(T*)ctx; }), setter_([](void* ctx, T val){ *(T*)ctx = val; }) {}
|
||||
Property& operator*() { return *this; }
|
||||
Property* operator->() { return this; }
|
||||
|
||||
T read() const {
|
||||
return (*getter_)(ctx_);
|
||||
}
|
||||
|
||||
T exchange(std::optional<T> value) const {
|
||||
T old_value = (*getter_)(ctx_);
|
||||
if (value.has_value()) {
|
||||
(*setter_)(ctx_, value.value());
|
||||
}
|
||||
return old_value;
|
||||
}
|
||||
|
||||
void* ctx_;
|
||||
T(*getter_)(void*);
|
||||
void(*setter_)(void*, T);
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct Property<const T> {
|
||||
Property(void* ctx, T(*getter)(void*))
|
||||
: ctx_(ctx), getter_(getter) {}
|
||||
Property(const T* ctx)
|
||||
: ctx_(const_cast<T*>(ctx)), getter_([](void* ctx){ return *(const T*)ctx; }) {}
|
||||
Property& operator*() { return *this; }
|
||||
Property* operator->() { return this; }
|
||||
|
||||
T read() const {
|
||||
return (*getter_)(ctx_);
|
||||
}
|
||||
|
||||
void* ctx_;
|
||||
T(*getter_)(void*);
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,77 @@
|
||||
#ifndef __FIBRE_SIMPLE_SERDES
|
||||
#define __FIBRE_SIMPLE_SERDES
|
||||
|
||||
//#include "stream.hpp"
|
||||
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,94 @@
|
||||
/*[# This is the original template, thus the warning below does not apply to this file #]
|
||||
* ============================ WARNING ============================
|
||||
* ==== This is an autogenerated file. ====
|
||||
* ==== Any changes to this file will be lost when recompiling. ====
|
||||
* =================================================================
|
||||
*
|
||||
* This file contains base classes that correspond to the interfaces defined in
|
||||
* your interface file. The objects you publish should inherit from these
|
||||
* interfaces.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("s")
|
||||
|
||||
[%- macro rettype(func) %]
|
||||
[%- if not func.out -%]
|
||||
void
|
||||
[%- elif func.out | length == 1 -%]
|
||||
[[(func.out.values() | first).type.c_name]]
|
||||
[%- else -%]
|
||||
[% for arg in func.out.values() %][[arg.type]][[', ' if not loop.last]][% endfor %]
|
||||
[%- endif -%]
|
||||
[%- endmacro %]
|
||||
|
||||
[%- macro render_interface(intf) %]
|
||||
class [[intf.name | to_pascal_case]]Intf {
|
||||
public:
|
||||
[%- for intf in intf.interfaces -%]
|
||||
[[render_interface(intf) | indent(4)]]
|
||||
[%- endfor %]
|
||||
[%- for enum in intf.enums %]
|
||||
enum [[enum.name | to_pascal_case]] {
|
||||
[%- for k, value in enum['values'].items() %]
|
||||
[[((enum.name + k) | to_macro_case).ljust(32)]] = [% if enum.is_flags %]0x[['%08x' | format(value.value)]][% else %][[value.value]][% endif %],
|
||||
[%- endfor %]
|
||||
};
|
||||
[%- endfor %]
|
||||
|
||||
[%- for property in intf.attributes.values() %]
|
||||
[%- if property.type.fullname.startswith("fibre.Property") %]
|
||||
[%- if not property.c_getter and not property.c_setter %]
|
||||
template<typename T> static inline auto get_[[property.name]](T* obj) { return [[property.type.c_name]]{&obj->[[property.c_name]]}; }
|
||||
template<typename T> static inline void get_[[property.name]](T* obj, void* ptr) { new (ptr) [[property.type.c_name]]{&obj->[[property.c_name]]}; }[# these are for the set_endpoint_from_float function. This is unmaintainable and should go away #]
|
||||
[%- elif not property.c_setter %]
|
||||
template<typename T> static inline auto get_[[property.name]](T* obj) { return [[property.type.c_name]]{obj, [](void* ctx){ return ([[property.type.value_type.c_name]])((T*)ctx)->[[property.c_getter]]; }}; }
|
||||
template<typename T> static inline void get_[[property.name]](T* obj, void* ptr) { new (ptr) [[property.type.c_name]]{obj, [](void* ctx){ return ([[property.type.value_type.c_name]])((T*)ctx)->[[property.c_getter]]; }}; }
|
||||
[%- else %]
|
||||
template<typename T> static inline auto get_[[property.name]](T* obj) { return [[property.type.c_name]]{obj, [](void* ctx){ return ([[property.type.value_type.c_name]])((T*)ctx)->[[property.c_getter]]; }, [](void* ctx, [[property.type.value_type.c_name]] value){ ((T*)ctx)->[[property.c_setter]](value); }}; }
|
||||
template<typename T> static inline void get_[[property.name]](T* obj, void* ptr) { new (ptr) [[property.type.c_name]]{obj, [](void* ctx){ return ([[property.type.value_type.c_name]])((T*)ctx)->[[property.c_getter]]; }, [](void* ctx, [[property.type.value_type.c_name]] value){ ((T*)ctx)->[[property.c_setter]](value); }}; }
|
||||
[%- endif %]
|
||||
[%- else %]
|
||||
template<typename T> static inline auto get_[[property.name]](T* obj) { return &obj->[[property.c_name]]; }
|
||||
[%- endif %]
|
||||
[%- endfor %]
|
||||
|
||||
[%- for func in intf.functions.values() %]
|
||||
virtual [[rettype(func)]] [[func.name | to_snake_case]]([% for in in func.in.values() %][% if loop.index0 %][[in.type.c_name]] [[in.name]][[', ' if not loop.last]][% endif %][% endfor %]) = 0;
|
||||
[%- endfor %]
|
||||
[%- for func in intf.functions.values() %]
|
||||
[%- for k, arg in func.in.items() | skip_first %]
|
||||
[[arg.type.c_name]] [[func.name | to_snake_case]]_in_[[arg.name]]_; // for internal use by Fibre
|
||||
template<typename T> static auto get_[[func.name | to_snake_case]]_in_[[arg.name]]_(T* obj) { return Property<[[arg.type.c_name]]>{&obj->[[func.name | to_snake_case]]_in_[[arg.name]]_}; }
|
||||
template<typename T> static void get_[[func.name | to_snake_case]]_in_[[arg.name]]_(T* obj, void* ptr) { new (ptr) Property<[[arg.type.c_name]]>{&obj->[[func.name | to_snake_case]]_in_[[arg.name]]_}; }
|
||||
[%- endfor %]
|
||||
[%- for k, arg in func.out.items() %]
|
||||
[[arg.type.c_name]] [[func.name | to_snake_case]]_out_[[arg.name]]_; // for internal use by Fibre
|
||||
template<typename T> static auto get_[[func.name | to_snake_case]]_out_[[arg.name]]_(T* obj) { return Property<const [[arg.type.c_name]]>{&obj->[[func.name | to_snake_case]]_out_[[arg.name]]_}; }
|
||||
template<typename T> static void get_[[func.name | to_snake_case]]_out_[[arg.name]]_(T* obj, void* ptr) { new (ptr) Property<const [[arg.type.c_name]]>{&obj->[[func.name | to_snake_case]]_out_[[arg.name]]_}; }
|
||||
[%- endfor %]
|
||||
[%- endfor %]
|
||||
};
|
||||
[%- endmacro %]
|
||||
|
||||
[% for intf in toplevel_interfaces %]
|
||||
[[render_interface(intf)]]
|
||||
[% endfor %]
|
||||
|
||||
[%- for _, enum in value_types.items() %]
|
||||
[%- if enum.is_flags %]
|
||||
// this is technically not thread-safe but practically it might be
|
||||
inline [[enum.c_name]] operator | ([[enum.c_name]] a, [[enum.c_name]] b) { return static_cast<[[enum.c_name]]>(static_cast<std::underlying_type_t<[[enum.c_name]]>>(a) | static_cast<std::underlying_type_t<[[enum.c_name]]>>(b)); }
|
||||
inline [[enum.c_name]] operator & ([[enum.c_name]] a, [[enum.c_name]] b) { return static_cast<[[enum.c_name]]>(static_cast<std::underlying_type_t<[[enum.c_name]]>>(a) & static_cast<std::underlying_type_t<[[enum.c_name]]>>(b)); }
|
||||
inline [[enum.c_name]] operator ^ ([[enum.c_name]] a, [[enum.c_name]] b) { return static_cast<[[enum.c_name]]>(static_cast<std::underlying_type_t<[[enum.c_name]]>>(a) ^ static_cast<std::underlying_type_t<[[enum.c_name]]>>(b)); }
|
||||
inline [[enum.c_name]]& operator |= ([[enum.c_name]] &a, [[enum.c_name]] b) { return reinterpret_cast<[[enum.c_name]]&>(reinterpret_cast<std::underlying_type_t<[[enum.c_name]]>&>(a) |= static_cast<std::underlying_type_t<[[enum.c_name]]>>(b)); }
|
||||
inline [[enum.c_name]]& operator &= ([[enum.c_name]] &a, [[enum.c_name]] b) { return reinterpret_cast<[[enum.c_name]]&>(reinterpret_cast<std::underlying_type_t<[[enum.c_name]]>&>(a) &= static_cast<std::underlying_type_t<[[enum.c_name]]>>(b)); }
|
||||
inline [[enum.c_name]]& operator ^= ([[enum.c_name]] &a, [[enum.c_name]] b) { return reinterpret_cast<[[enum.c_name]]&>(reinterpret_cast<std::underlying_type_t<[[enum.c_name]]>&>(a) ^= static_cast<std::underlying_type_t<[[enum.c_name]]>>(b)); }
|
||||
inline [[enum.c_name]] operator ~ ([[enum.c_name]] a) { return static_cast<[[enum.c_name]]>(~static_cast<std::underlying_type_t<[[enum.c_name]]>>(a)); }
|
||||
[%- endif %]
|
||||
[%- endfor %]
|
||||
|
||||
|
||||
|
||||
#pragma GCC pop_options
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
tup.include('../tupfiles/build.lua')
|
||||
|
||||
fibre_package = define_package{
|
||||
sources={'protocol.cpp', 'posix_tcp.cpp', 'posix_udp.cpp'},
|
||||
libs={'pthread'},
|
||||
headers={'include'}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#include <thread>
|
||||
#include <future>
|
||||
#include <vector>
|
||||
|
||||
#include <fibre/protocol.hpp>
|
||||
|
||||
|
||||
#define TCP_RX_BUF_LEN 512
|
||||
|
||||
class TCPStreamSink : public StreamSink {
|
||||
public:
|
||||
TCPStreamSink(int socket_fd) :
|
||||
socket_fd_(socket_fd)
|
||||
{}
|
||||
|
||||
int process_bytes(const uint8_t* buffer, size_t length, size_t* processed_bytes) {
|
||||
int bytes_sent = send(socket_fd_, buffer, length, 0);
|
||||
if (processed_bytes)
|
||||
*processed_bytes = (bytes_sent == -1) ? 0 : bytes_sent;
|
||||
return (bytes_sent == -1) ? -1 : 0;
|
||||
}
|
||||
|
||||
size_t get_free_space() { return SIZE_MAX; }
|
||||
|
||||
private:
|
||||
int socket_fd_;
|
||||
};
|
||||
|
||||
|
||||
int serve_client(int sock_fd) {
|
||||
uint8_t buf[TCP_RX_BUF_LEN];
|
||||
|
||||
// initialize output stack for this client
|
||||
TCPStreamSink tcp_packet_output(sock_fd);
|
||||
StreamBasedPacketSink packet2stream(tcp_packet_output);
|
||||
BidirectionalPacketBasedChannel channel(packet2stream);
|
||||
|
||||
StreamToPacketSegmenter stream2packet(channel);
|
||||
|
||||
// now listen for it
|
||||
for (;;) {
|
||||
memset(buf, 0, sizeof(buf));
|
||||
// returns as soon as there is some data
|
||||
ssize_t n_received = recv(sock_fd, buf, sizeof(buf), 0);
|
||||
|
||||
// -1 indicates error and 0 means that the client gracefully terminated
|
||||
if (n_received == -1 || n_received == 0) {
|
||||
close(sock_fd);
|
||||
return n_received;
|
||||
}
|
||||
|
||||
// input processing stack
|
||||
size_t processed = 0;
|
||||
stream2packet.process_bytes(buf, n_received, &processed);
|
||||
}
|
||||
}
|
||||
|
||||
// function to check if a worker thread handling a single client is done
|
||||
template<typename T>
|
||||
bool future_is_ready(std::future<T>& t){
|
||||
return t.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
|
||||
}
|
||||
|
||||
int serve_on_tcp(unsigned int port) {
|
||||
struct sockaddr_in6 si_me, si_other;
|
||||
int s;
|
||||
|
||||
|
||||
if ((s=socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)) == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset((char *) &si_me, 0, sizeof(si_me));
|
||||
si_me.sin6_family = AF_INET6;
|
||||
si_me.sin6_port = htons(port);
|
||||
si_me.sin6_flowinfo = 0;
|
||||
si_me.sin6_addr = in6addr_any;
|
||||
if (bind(s, reinterpret_cast<struct sockaddr *>(&si_me), sizeof(si_me)) == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
listen(s, 128); // make this socket a passive socket
|
||||
std::vector<std::future<int>> serv_pool;
|
||||
for (;;) {
|
||||
memset(&si_other, 0, sizeof(si_other));
|
||||
|
||||
socklen_t silen = sizeof(si_other);
|
||||
// TODO: Add a limit on accepting connections
|
||||
int client_portal_fd = accept(s, reinterpret_cast<sockaddr *>(&si_other), &silen); // blocking call
|
||||
serv_pool.push_back(std::async(std::launch::async, serve_client, client_portal_fd));
|
||||
// do a little clean up on the pool
|
||||
for (std::vector<std::future<int>>::iterator it = serv_pool.end()-1; it >= serv_pool.begin(); --it) {
|
||||
if (future_is_ready(*it)) {
|
||||
// we can erase this thread
|
||||
serv_pool.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close(s);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <fibre/protocol.hpp>
|
||||
|
||||
#define UDP_RX_BUF_LEN 512
|
||||
#define UDP_TX_BUF_LEN 512
|
||||
|
||||
|
||||
class UDPPacketSender : public PacketSink {
|
||||
public:
|
||||
UDPPacketSender(int socket_fd, struct sockaddr_in6 *si_other) :
|
||||
_socket_fd(socket_fd),
|
||||
_si_other(si_other)
|
||||
{}
|
||||
|
||||
size_t get_mtu() { return UDP_TX_BUF_LEN; }
|
||||
|
||||
int process_packet(const uint8_t* buffer, size_t length) {
|
||||
// cannot send partial packets
|
||||
if (length > get_mtu())
|
||||
return -1;
|
||||
|
||||
int status = sendto(_socket_fd, buffer, length, 0, reinterpret_cast<struct sockaddr*>(_si_other), sizeof(*_si_other));
|
||||
return (status == -1) ? -1 : 0;
|
||||
}
|
||||
|
||||
private:
|
||||
int _socket_fd;
|
||||
struct sockaddr_in6 *_si_other;
|
||||
};
|
||||
|
||||
|
||||
|
||||
int serve_on_udp(unsigned int port) {
|
||||
struct sockaddr_in6 si_me, si_other;
|
||||
int s;
|
||||
socklen_t slen = sizeof(si_other);
|
||||
uint8_t buf[UDP_RX_BUF_LEN];
|
||||
|
||||
if ((s=socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)) == -1)
|
||||
return -1;
|
||||
|
||||
memset((char *) &si_me, 0, sizeof(si_me));
|
||||
si_me.sin6_family = AF_INET6;
|
||||
si_me.sin6_port = htons(port);
|
||||
si_me.sin6_flowinfo = 0;
|
||||
si_me.sin6_addr= in6addr_any;
|
||||
if (bind(s, reinterpret_cast<struct sockaddr *>(&si_me), sizeof(si_me)) == -1)
|
||||
return -1;
|
||||
|
||||
for (;;) {
|
||||
ssize_t n_received = recvfrom(s, buf, sizeof(buf), 0, reinterpret_cast<struct sockaddr *>(&si_other), &slen);
|
||||
if (n_received == -1)
|
||||
return -1;
|
||||
//printf("Received packet from %s:%d\nData: %s\n\n",
|
||||
// inet_ntoa(si_other.sin_addr), ntohs(si_other.sin_port), buf);
|
||||
|
||||
UDPPacketSender udp_packet_output(s, &si_other);
|
||||
BidirectionalPacketBasedChannel udp_channel(udp_packet_output);
|
||||
udp_channel.process_packet(buf, n_received);
|
||||
}
|
||||
|
||||
close(s);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
|
||||
/* Includes ------------------------------------------------------------------*/
|
||||
|
||||
#include <memory>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <fibre/protocol.hpp>
|
||||
#include <fibre/crc.hpp>
|
||||
|
||||
/* Private defines -----------------------------------------------------------*/
|
||||
/* Private macros ------------------------------------------------------------*/
|
||||
/* Private typedef -----------------------------------------------------------*/
|
||||
/* Global constant data ------------------------------------------------------*/
|
||||
/* Global variables ----------------------------------------------------------*/
|
||||
|
||||
/* Private constant data -----------------------------------------------------*/
|
||||
/* Private variables ---------------------------------------------------------*/
|
||||
/* Private function prototypes -----------------------------------------------*/
|
||||
|
||||
static void hexdump(const uint8_t* buf, size_t len);
|
||||
|
||||
/* Function implementations --------------------------------------------------*/
|
||||
|
||||
#if 0
|
||||
void hexdump(const uint8_t* buf, size_t len) {
|
||||
for (size_t pos = 0; pos < len; ++pos) {
|
||||
printf(" %02x", buf[pos]);
|
||||
if ((((pos + 1) % 16) == 0) || ((pos + 1) == len))
|
||||
printf("\r\n");
|
||||
osDelay(2);
|
||||
}
|
||||
}
|
||||
#else
|
||||
void hexdump(const uint8_t* buf, size_t len) {
|
||||
(void) buf;
|
||||
(void) len;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
int StreamToPacketSegmenter::process_bytes(const uint8_t *buffer, size_t length, size_t* processed_bytes) {
|
||||
int result = 0;
|
||||
|
||||
while (length--) {
|
||||
if (header_index_ < sizeof(header_buffer_)) {
|
||||
// Process header byte
|
||||
header_buffer_[header_index_++] = *buffer;
|
||||
if (header_index_ == 1 && header_buffer_[0] != CANONICAL_PREFIX) {
|
||||
header_index_ = 0;
|
||||
} else if (header_index_ == 2 && (header_buffer_[1] & 0x80)) {
|
||||
header_index_ = 0; // TODO: support packets larger than 128 bytes
|
||||
} else if (header_index_ == 3 && calc_crc8<CANONICAL_CRC8_POLYNOMIAL>(CANONICAL_CRC8_INIT, header_buffer_, 3)) {
|
||||
header_index_ = 0;
|
||||
} else if (header_index_ == 3) {
|
||||
packet_length_ = header_buffer_[1] + 2;
|
||||
}
|
||||
} else if (packet_index_ < sizeof(packet_buffer_)) {
|
||||
// Process payload byte
|
||||
packet_buffer_[packet_index_++] = *buffer;
|
||||
}
|
||||
|
||||
// If both header and packet are fully received, hand it on to the packet processor
|
||||
if (header_index_ == 3 && packet_index_ == packet_length_) {
|
||||
if (calc_crc16<CANONICAL_CRC16_POLYNOMIAL>(CANONICAL_CRC16_INIT, packet_buffer_, packet_length_) == 0) {
|
||||
result |= output_.process_packet(packet_buffer_, packet_length_ - 2);
|
||||
}
|
||||
header_index_ = packet_index_ = packet_length_ = 0;
|
||||
}
|
||||
buffer++;
|
||||
if (processed_bytes)
|
||||
(*processed_bytes)++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int StreamBasedPacketSink::process_packet(const uint8_t *buffer, size_t length) {
|
||||
// TODO: support buffer size >= 128
|
||||
if (length >= 128)
|
||||
return -1;
|
||||
|
||||
LOG_FIBRE("send header\r\n");
|
||||
uint8_t header[] = {
|
||||
CANONICAL_PREFIX,
|
||||
static_cast<uint8_t>(length),
|
||||
0
|
||||
};
|
||||
header[2] = calc_crc8<CANONICAL_CRC8_POLYNOMIAL>(CANONICAL_CRC8_INIT, header, 2);
|
||||
|
||||
if (output_.process_bytes(header, sizeof(header), nullptr))
|
||||
return -1;
|
||||
LOG_FIBRE("send payload:\r\n");
|
||||
hexdump(buffer, length);
|
||||
if (output_.process_bytes(buffer, length, nullptr))
|
||||
return -1;
|
||||
|
||||
LOG_FIBRE("send crc16\r\n");
|
||||
uint16_t crc16 = calc_crc16<CANONICAL_CRC16_POLYNOMIAL>(CANONICAL_CRC16_INIT, buffer, length);
|
||||
uint8_t crc16_buffer[] = {
|
||||
(uint8_t)((crc16 >> 8) & 0xff),
|
||||
(uint8_t)((crc16 >> 0) & 0xff)
|
||||
};
|
||||
if (output_.process_bytes(crc16_buffer, 2, nullptr))
|
||||
return -1;
|
||||
LOG_FIBRE("sent!\r\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// Returns part of the JSON interface definition.
|
||||
bool fibre::endpoint0_handler(fibre::cbufptr_t* input_buffer, fibre::bufptr_t* output_buffer) {
|
||||
// The request must contain a 32 bit integer to specify an offset
|
||||
std::optional<uint32_t> offset = read_le<uint32_t>(input_buffer);
|
||||
|
||||
if (!offset.has_value()) {
|
||||
// Didn't receive any offset
|
||||
return false;
|
||||
} else if (offset.value() == 0xffffffff) {
|
||||
// If the offset is special value 0xFFFFFFFF, send back the JSON version ID instead
|
||||
return write_le<uint32_t>(json_version_id_, output_buffer);
|
||||
} else if (offset.value() >= embedded_json_length) {
|
||||
// Attempt to read beyond the buffer end - return empty response
|
||||
return true;
|
||||
} else {
|
||||
// Return part of the json file
|
||||
size_t n_copy = std::min(output_buffer->size(), embedded_json_length - (size_t)offset.value());
|
||||
memcpy(output_buffer->begin(), embedded_json + offset.value(), n_copy);
|
||||
*output_buffer = output_buffer->skip(n_copy);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
int BidirectionalPacketBasedChannel::process_packet(const uint8_t* buffer, size_t length) {
|
||||
LOG_FIBRE("got packet of length %d: \r\n", length);
|
||||
hexdump(buffer, length);
|
||||
if (length < 4)
|
||||
return -1;
|
||||
|
||||
uint16_t seq_no = read_le<uint16_t>(&buffer, &length);
|
||||
|
||||
if (seq_no & 0x8000) {
|
||||
// TODO: ack handling
|
||||
} else {
|
||||
// TODO: think about some kind of ordering guarantees
|
||||
// currently the seq_no is just used to associate a response with a request
|
||||
|
||||
uint16_t endpoint_id = read_le<uint16_t>(&buffer, &length);
|
||||
bool expect_response = endpoint_id & 0x8000;
|
||||
endpoint_id &= 0x7fff;
|
||||
|
||||
// Verify packet trailer. The expected trailer value depends on the selected endpoint.
|
||||
// For endpoint 0 this is just the protocol version, for all other endpoints it's a
|
||||
// CRC over the entire JSON descriptor tree (this may change in future versions).
|
||||
uint16_t expected_trailer = endpoint_id ? fibre::json_crc_ : PROTOCOL_VERSION;
|
||||
uint16_t actual_trailer = buffer[length - 2] | (buffer[length - 1] << 8);
|
||||
if (expected_trailer != actual_trailer) {
|
||||
LOG_FIBRE("trailer mismatch for endpoint %d: expected %04x, got %04x\r\n", endpoint_id, expected_trailer, actual_trailer);
|
||||
return -1;
|
||||
}
|
||||
LOG_FIBRE("trailer ok for endpoint %d\r\n", endpoint_id);
|
||||
|
||||
// TODO: if more bytes than the MTU were requested, should we abort or just return as much as possible?
|
||||
|
||||
uint16_t expected_response_length = read_le<uint16_t>(&buffer, &length);
|
||||
|
||||
// Limit response length according to our local TX buffer size
|
||||
if (expected_response_length > sizeof(tx_buf_) - 2)
|
||||
expected_response_length = sizeof(tx_buf_) - 2;
|
||||
|
||||
fibre::cbufptr_t input_buffer{buffer, length - 2};
|
||||
fibre::bufptr_t output_buffer{tx_buf_ + 2, expected_response_length};
|
||||
fibre::endpoint_handler(endpoint_id, &input_buffer, &output_buffer);
|
||||
|
||||
// Send response
|
||||
if (expect_response) {
|
||||
size_t actual_response_length = expected_response_length - output_buffer.size() + 2;
|
||||
write_le<uint16_t>(seq_no | 0x8000, tx_buf_);
|
||||
|
||||
LOG_FIBRE("send packet:\r\n");
|
||||
hexdump(tx_buf_, actual_response_length);
|
||||
output_.process_packet(tx_buf_, actual_response_length);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*[# This is the original template, thus the warning below does not apply to this file #]
|
||||
* ============================ WARNING ============================
|
||||
* ==== This is an autogenerated file. ====
|
||||
* ==== Any changes to this file will be lost when recompiling. ====
|
||||
* =================================================================
|
||||
*
|
||||
* This file contains support functions for the ODrive ASCII protocol.
|
||||
*
|
||||
* TODO: might generalize this as an approach to runtime introspection.
|
||||
*/
|
||||
|
||||
#include <fibre/introspection.hpp>
|
||||
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("s")
|
||||
|
||||
[% for intf in interfaces.values() %][% if not intf.builtin %]
|
||||
template<typename T>
|
||||
struct [[intf.fullname | to_pascal_case]]TypeInfo : TypeInfo {
|
||||
using TypeInfo::TypeInfo;
|
||||
static const PropertyInfo property_table[];
|
||||
static const [[intf.fullname | to_pascal_case]]TypeInfo<T> singleton;
|
||||
static Introspectable make_introspectable(T& obj) { return TypeInfo::make_introspectable(&obj, &singleton); }
|
||||
|
||||
introspectable_storage_t get_child(introspectable_storage_t obj, size_t idx) const override {
|
||||
T* ptr = *(T**)&obj;
|
||||
introspectable_storage_t res;
|
||||
switch (idx) {
|
||||
[%- for property in intf.attributes.values() %]
|
||||
case [[loop.index0]]: *(decltype([[intf.c_name]]::get_[[property.name]](std::declval<T*>()))*)(&res) = [[intf.c_name]]::get_[[property.name]](ptr); break;
|
||||
[%- endfor %]
|
||||
}
|
||||
return res;
|
||||
}
|
||||
};
|
||||
[% endif %][% endfor %]
|
||||
|
||||
[% for intf in interfaces.values() %][% if not intf.builtin %]
|
||||
template<typename T>
|
||||
const PropertyInfo [[intf.fullname | to_pascal_case]]TypeInfo<T>::property_table[] = {
|
||||
[%- for property in intf.attributes.values() %]
|
||||
{"[[property.name]]", &[[(property.type.purename or property.type.fullname) | to_pascal_case]]TypeInfo<std::remove_reference_t<decltype(*[[intf.c_name]]::get_[[property.name]](std::declval<T*>()))>>::singleton},
|
||||
[%- endfor %]
|
||||
};
|
||||
template<typename T>
|
||||
const [[intf.fullname | to_pascal_case]]TypeInfo<T> [[intf.fullname | to_pascal_case]]TypeInfo<T>::singleton{[[intf.fullname | to_pascal_case]]TypeInfo<T>::property_table, sizeof([[intf.fullname | to_pascal_case]]TypeInfo<T>::property_table) / sizeof([[intf.fullname | to_pascal_case]]TypeInfo<T>::property_table[0])};
|
||||
|
||||
[% endif %][% endfor %]
|
||||
|
||||
#pragma GCC pop_options
|
||||
Reference in New Issue
Block a user