Program Listing for File channel.hpp

Return to documentation for file (libspookyaction/include/pn532/channel.hpp)

//
// Created by spak on 3/3/21.
//

#ifndef PN532_CHANNEL_REPL_HPP
#define PN532_CHANNEL_REPL_HPP

#include <chrono>
#include <mlab/bin_data.hpp>
#include <mlab/result.hpp>
#include <mlab/time.hpp>
#include <pn532/bits.hpp>
#include <pn532/log.h>
#include <pn532/msg.hpp>

namespace pn532 {
    using mlab::bin_data;
    using mlab::bin_stream;
    using ms = std::chrono::milliseconds;

    enum struct frame_type {
        ack,
        nack,
        info,
        error
    };


    template <frame_type>
    struct frame {};

    template <>
    struct frame<frame_type::info> {
        bits::transport transport = bits::transport::host_to_pn532;
        command_code command = command_code::diagnose;
        bin_data data;
    };

    class any_frame : public mlab::any_of<frame_type, frame, frame_type::error> {
    public:
        using mlab::any_of<frame_type, frame, frame_type::error>::any_of;
    };

    struct frame_id {
        static constexpr std::size_t min_frame_length =
                bits::start_of_packet_code.size() + std::max(std::max(bits::ack_packet_code.size(), bits::nack_packet_code.size()), bits::fixed_extended_packet_length.size());
        static constexpr std::size_t max_min_info_frame_header_length = min_frame_length + 1 /* preamble */ + 3 /* extended info frame length */;

        frame_type type = frame_type::error;

        bool has_preamble = false;

        std::size_t frame_total_length = min_frame_length;

        std::size_t info_frame_data_size = 0;
    };

#ifndef DOXYGEN_SHOULD_SKIP_THIS
    bin_data &operator<<(bin_data &bd, frame<frame_type::ack> const &);
    bin_data &operator<<(bin_data &bd, frame<frame_type::nack> const &);
    bin_data &operator<<(bin_data &bd, frame<frame_type::error> const &);
    bin_data &operator<<(bin_data &bd, frame<frame_type::info> const &f);
    bin_data &operator<<(bin_data &bd, any_frame const &f);
    bin_stream &operator>>(bin_stream &s, any_frame &f);

    bin_stream &operator>>(std::tuple<bin_stream &, frame_id const &> s_id, any_frame &f);

    bin_stream &operator>>(bin_stream &s, frame_id &id);

#endif

    enum struct channel_error {
        timeout,
        hw_error,
        malformed,
        app_error
    };

    template <class... Tn>
    using result = mlab::result<channel_error, Tn...>;


    enum struct comm_dir {
        send,
        receive
    };

    enum struct comm_rx_mode {
        stream,
        buffered
    };

    class channel {
    public:
        virtual ~channel() = default;

    protected:
        friend class comm_operation;

        virtual result<> raw_send(mlab::range<bin_data::const_iterator> buffer, ms timeout) = 0;

        virtual result<> raw_receive(mlab::range<bin_data::iterator> buffer, ms timeout) = 0;

        [[nodiscard]] virtual comm_rx_mode raw_receive_mode() const = 0;

        virtual bool on_receive_prepare(ms timeout) { return true; }
        virtual void on_receive_complete(result<> const &outcome) {}
        virtual bool on_send_prepare(ms timeout) { return true; }
        virtual void on_send_complete(result<> const &outcome) {}
        result<> send(any_frame const &frame, ms timeout);

        [[nodiscard]] result<any_frame> receive(ms timeout);

    public:
        virtual bool wake() = 0;

        result<> send_ack(bool ack_value, ms timeout);

        result<> receive_ack(bool ack_value, ms timeout);

        result<> command(command_code cmd, bin_data data, ms timeout);

        [[nodiscard]] result<bin_data> response(command_code cmd, ms timeout);

        [[nodiscard]] result<bin_data> command_response(command_code cmd, bin_data data, ms timeout);

        template <mlab::is_extractable Data>
        [[nodiscard]] result<Data> command_parse_response(command_code cmd, bin_data data, ms timeout);

    private:
        [[nodiscard]] result<any_frame> receive_stream(ms timeout);

        [[nodiscard]] result<any_frame> receive_restart(ms timeout);

        bool _has_operation = false;
    };

#ifndef DOXYGEN_SHOULD_SKIP_THIS
    [[nodiscard]] const char *to_string(frame_type type);

    [[nodiscard]] const char *to_string(channel_error e);
#endif

    class comm_operation {
        channel &_owner;
        comm_dir _event;
        result<> _result;

    public:
        comm_operation(channel &owner, comm_dir event, ms timeout);

        ~comm_operation();

        [[nodiscard]] inline bool ok() const;

        [[nodiscard]] inline channel_error error() const;

        [[nodiscard]] inline channel_error update(channel_error e);

        [[nodiscard]] inline result<> update(bool operation_result);

        template <class... Tn, class... Args>
        [[nodiscard]] inline result<Tn...> update(Args &&...args);
    };

}// namespace pn532

namespace pn532 {

    template <mlab::is_extractable Data>
    result<Data> channel::command_parse_response(command_code cmd, bin_data data, ms timeout) {
        if (const auto res_cmd = command_response(cmd, std::move(data), timeout); res_cmd) {
            bin_stream s{*res_cmd};
            auto retval = Data();
            s >> retval;
            if (s.bad()) {
                PN532_LOGE("%s: could not parse result from response data.", to_string(cmd));
                return channel_error::malformed;
            }
            if (not s.eof()) {
                PN532_LOGW("%s: stray data in response (%d bytes).", to_string(cmd), s.remaining());
            }
            return retval;
        } else {
            return res_cmd.error();
        }
    }

    bool comm_operation::ok() const {
        return bool(_result);
    }

    channel_error comm_operation::error() const {
        return _result.error();
    }

    channel_error comm_operation::update(channel_error e) {
        _result = e;
        return e;
    }

    result<> comm_operation::update(bool operation_result) {
        if (operation_result) {
            _result = mlab::result_success;
        } else {
            _result = channel_error::timeout;
        }
        return _result;
    }

    template <class... Tn, class... Args>
    result<Tn...> comm_operation::update(Args &&...args) {
        result<Tn...> retval{std::forward<Args>(args)...};
        if (retval) {
            _result = mlab::result_success;
        } else {
            _result = retval.error();
        }
        return retval;
    }
}// namespace pn532
#endif//PN532_CHANNEL_REPL_HPP