This commit is contained in:
2025-05-13 01:34:53 +03:00
parent 427735e23d
commit 83f3f1c7d4
945 changed files with 633484 additions and 0 deletions
@@ -0,0 +1,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