Program Listing for File tag.hpp

Return to documentation for file (libspookyaction/include/desfire/tag.hpp)

//
// Created by Pietro Saccardi on 02/01/2021.
//

#ifndef DESFIRE_TAG_HPP
#define DESFIRE_TAG_HPP

#include <desfire/cipher_provider.hpp>
#include <desfire/data.hpp>
#include <desfire/keys.hpp>
#include <desfire/msg.hpp>
#include <desfire/pcd.hpp>
#include <desfire/protocol.hpp>
#include <list>
#include <memory>
#include <mlab/result.hpp>
#include <pn532/bits.hpp>
#include <type_traits>

namespace pn532 {
    class controller;
}

namespace ut::desfire_exchanges {
    struct session;
}

namespace desfire {
    using mlab::bin_stream;
    using mlab::lsb_t;


    struct trust_card_t {};

    static constexpr trust_card_t trust_card{};

    static constexpr std::uint32_t all_records = 0;

    static constexpr std::uint32_t all_data = 0;

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

    struct comm_cfg {
        comm_mode tx = comm_mode::plain;
        comm_mode rx = comm_mode::plain;
        std::size_t tx_secure_data_offset = 0;
        bool is_validated = false;

        inline constexpr comm_cfg(comm_mode txrx, std::size_t sec_data_ofs = 1, bool validated = false);

        inline constexpr comm_cfg(comm_mode tx, comm_mode rx, std::size_t sec_data_ofs = 1, bool validated = false);
    };


    template <class T>
    concept is_parsable_reponse_t = mlab::is_extractable<T> or std::is_integral_v<T>;

    class tag : public std::enable_shared_from_this<tag> {
    public:
        tag(std::shared_ptr<pn532::controller> ctrl, std::uint8_t logical_index);

        explicit tag(std::shared_ptr<desfire::pcd> pcd);

        tag(std::shared_ptr<desfire::pcd> pcd, std::unique_ptr<cipher_provider> provider);

        tag(tag const &) = delete;
        tag(tag &&) = default;
        tag &operator=(tag const &) = delete;
        tag &operator=(tag &&) = default;
        result<bin_data> raw_command_response(bin_stream &tx_data, bool rx_fetch_additional_frames);

        result<bits::status, bin_data> command_status_response(bits::command_code cmd, bin_data const &payload, comm_cfg const &cfg, bool rx_fetch_additional_frames = true, protocol *override_protocol = nullptr);

        result<bin_data> command_response(bits::command_code cmd, bin_data const &payload, comm_cfg const &cfg, bool rx_fetch_additional_frames = true, protocol *override_protocol = nullptr);

        template <is_parsable_reponse_t Data>
        result<Data> command_parse_response(bits::command_code cmd, bin_data const &payload, comm_cfg const &cfg);

        [[nodiscard]] inline app_id const &active_app() const;

        [[nodiscard]] inline cipher_type active_cipher_type() const;

        [[nodiscard]] inline std::uint8_t active_key_no() const;

        result<> authenticate(any_key const &k);

        template <cipher_type Type>
        result<> authenticate(key<Type> const &k);

        result<> select_application(app_id const &aid = root_app);

        result<> create_application(app_id const &aid, app_settings settings);

        result<> change_app_settings(key_rights new_rights);

        [[nodiscard]] result<app_settings> get_app_settings();

        [[nodiscard]] result<std::uint8_t> get_key_version(std::uint8_t key_no);

        [[nodiscard]] result<std::vector<app_id>> get_application_ids();

        result<> delete_application(app_id const &aid);

        [[nodiscard]] result<manufacturing_info> get_info();


        result<> format_picc();

        result<> change_key(any_key const &new_key);

        template <cipher_type Type>
        result<> change_key(key<Type> const &new_key);

        result<> change_key(any_key const &previous_key, any_key const &new_key);

        template <cipher_type Type>
        result<> change_key(key<Type> const &previous_key, key<Type> const &new_key);

        [[nodiscard]] result<std::vector<file_id>> get_file_ids();

        [[nodiscard]] result<any_file_settings> get_file_settings(file_id fid);

        template <file_type Type>
        [[nodiscard]] result<file_settings<Type>> get_specific_file_settings(file_id fid);

        result<> change_file_settings(file_id fid, common_file_settings const &settings, trust_card_t);

        result<> change_file_settings(file_id fid, common_file_settings const &settings, comm_mode operation_mode);

        result<> create_file(file_id fid, file_settings<file_type::standard> const &settings);

        result<> create_file(file_id fid, any_file_settings const &settings);


        result<> create_file(file_id fid, file_settings<file_type::backup> const &settings);

        result<> create_file(file_id fid, file_settings<file_type::value> const &settings);

        result<> create_file(file_id fid, file_settings<file_type::linear_record> const &settings);

        result<> create_file(file_id fid, file_settings<file_type::cyclic_record> const &settings);

        result<> delete_file(file_id fid);

        result<> clear_record_file(file_id fid);

        result<> commit_transaction();

        result<> abort_transaction();

        [[nodiscard]] result<bin_data> read_data(file_id fid, trust_card_t, std::uint32_t offset = 0, std::uint32_t length = all_data);

        [[nodiscard]] result<bin_data> read_data(file_id fid, comm_mode operation_mode, std::uint32_t offset = 0, std::uint32_t length = all_data);

        result<> write_data(file_id fid, bin_data const &data, trust_card_t, std::uint32_t offset = 0);

        result<> write_data(file_id fid, bin_data const &data, comm_mode operation_mode, std::uint32_t offset = 0);

        [[nodiscard]] result<std::int32_t> get_value(file_id fid, trust_card_t);

        [[nodiscard]] result<std::int32_t> get_value(file_id fid, comm_mode operation_mode);

        result<> credit(file_id fid, std::int32_t amount, trust_card_t);

        result<> credit(file_id fid, std::int32_t amount, comm_mode operation_mode);

        result<> limited_credit(file_id fid, std::int32_t amount, trust_card_t);

        result<> limited_credit(file_id fid, std::int32_t amount, comm_mode operation_mode);

        result<> debit(file_id fid, std::int32_t amount, trust_card_t);

        result<> debit(file_id fid, std::int32_t amount, comm_mode operation_mode);

        result<> write_record(file_id fid, bin_data const &data, trust_card_t, std::uint32_t offset = 0);

        result<> write_record(file_id fid, bin_data const &data, comm_mode operation_mode, std::uint32_t offset = 0);

        template <class T>
        result<> write_record(file_id fid, T &&record, trust_card_t);

        template <class T>
        result<> write_record(file_id fid, T &&record, comm_mode operation_mode);

        template <class T>
        [[nodiscard]] result<std::vector<T>> read_parse_records(file_id fid, trust_card_t, std::uint32_t record_index = 0, std::uint32_t record_count = all_records);

        template <class T>
        [[nodiscard]] result<std::vector<T>> read_parse_records(file_id fid, comm_mode operation_mode, std::uint32_t record_index = 0, std::uint32_t record_count = all_records);

        [[nodiscard]] result<bin_data> read_records(file_id fid, trust_card_t, std::uint32_t record_index = 0, std::uint32_t record_count = all_records);

        [[nodiscard]] result<bin_data> read_records(file_id fid, std::uint32_t record_index, std::uint32_t record_count, comm_mode operation_mode);

        [[nodiscard]] result<pn532::nfcid_2t> get_card_uid();

        [[nodiscard]] result<std::uint32_t> get_free_mem();

        result<> set_configuration(bool allow_format = true, bool enable_random_id = false);

        [[nodiscard]] static comm_mode determine_operation_mode(file_access requested_access, file_access_rights const &file_rights, file_security security);

        [[nodiscard]] static comm_mode determine_operation_mode(file_access requested_access, common_file_settings const &settings);

        [[nodiscard]] static comm_mode determine_operation_mode(file_access requested_access, any_file_settings const &settings);

        [[nodiscard]] result<comm_mode> determine_operation_mode(file_access requested_access, file_id fid);

    private:
        friend struct ut::desfire_exchanges::session;

        template <cipher_type Cipher>
        void ut_init_session(desfire::key<Cipher> const &session_key, desfire::app_id app, std::uint8_t key_no);


        template <class T>
        [[nodiscard]] static std::vector<T> parse_records(bin_data const &data, std::uint32_t exp_count);

        [[nodiscard]] static result<> safe_drop_payload(bits::command_code cmd, result<bin_data> const &result);
        static void log_not_empty(bits::command_code cmd, range<bin_data::const_iterator> data);

        [[nodiscard]] inline desfire::pcd &pcd();

        result<> change_key_internal(any_key const *previous_key, any_key const &new_key);

        result<> change_file_settings_internal(file_id fid, common_file_settings const &settings, comm_mode operation_mode, bool validated);
        result<bin_data> read_data_internal(file_id fid, comm_mode operation_mode, std::uint32_t offset, std::uint32_t length, bool validated);
        result<> write_data_internal(file_id fid, bin_data const &data, comm_mode operation_mode, std::uint32_t offset, bool validated);
        result<std::int32_t> get_value_internal(file_id fid, comm_mode operation_mode, bool validated);
        result<> write_value_internal(bits::command_code cmd, file_id fid, std::int32_t amount, comm_mode operation_mode, bool validated);
        result<> write_record_internal(file_id fid, bin_data const &data, comm_mode operation_mode, std::uint32_t offset, bool validated);
        result<bin_data> read_records_internal(file_id fid, std::uint32_t record_index, std::uint32_t record_count, comm_mode operation_mode, bool validated);
        void logout();

        [[nodiscard]] comm_cfg const &default_comm_cfg() const;
        [[nodiscard]] bool active_protocol_is_legacy() const;

        std::shared_ptr<desfire::pcd> _pcd;

        std::unique_ptr<cipher_provider> _provider;
        std::unique_ptr<protocol> _active_protocol;
        cipher_type _active_cipher_type;
        std::uint8_t _active_key_number;
        app_id _active_app;
    };
}// namespace desfire

namespace desfire {

    desfire::pcd &tag::pcd() {
        return *_pcd;
    }

    template <cipher_type Type>
    result<> tag::authenticate(key<Type> const &k) {
        return authenticate(any_key{k});
    }
    template <cipher_type Type>
    result<> tag::change_key(key<Type> const &new_key) {
        return change_key(any_key{new_key});
    }

    template <cipher_type Type>
    result<> tag::change_key(key<Type> const &previous_key, key<Type> const &new_key) {
        return change_key(any_key{previous_key}, any_key{new_key});
    }


    app_id const &tag::active_app() const {
        return _active_app;
    }
    cipher_type tag::active_cipher_type() const {
        return _active_cipher_type;
    }
    std::uint8_t tag::active_key_no() const {
        return _active_key_number;
    }

    constexpr comm_cfg::comm_cfg(comm_mode txrx, std::size_t sec_data_ofs, bool validated)
        : tx{txrx},
          rx{txrx},
          tx_secure_data_offset{sec_data_ofs},
          is_validated{validated} {}

    constexpr comm_cfg::comm_cfg(comm_mode tx, comm_mode rx, std::size_t sec_data_ofs, bool validated)
        : tx{tx},
          rx{rx},
          tx_secure_data_offset{sec_data_ofs},
          is_validated{validated} {}

    template <is_parsable_reponse_t Data>
    result<Data> tag::command_parse_response(bits::command_code cmd, bin_data const &payload, comm_cfg const &cfg) {
        const auto res_cmd = command_response(cmd, payload, cfg);
        if (not res_cmd) {
            return res_cmd.error();
        }
        bin_stream s{*res_cmd};
        auto data = Data();
        // Automatically add the ability to parse integral types with at least 16 bits as LSB.
        if constexpr (std::is_integral_v<Data> and sizeof(Data) > 1) {
            s >> lsb_t<sizeof(Data) * 8>{} >> data;
        } else {
            s >> data;
        }
        if (s.bad()) {
            DESFIRE_LOGE("%s: could not parse result from response data.", to_string(cmd));
            return error::malformed;
        } else if (not s.eof()) {
            log_not_empty(cmd, s.peek());
        }
        return data;
    }


    template <cipher_type Cipher>
    void tag::ut_init_session(desfire::key<Cipher> const &session_key, desfire::app_id app, std::uint8_t key_no) {
        _active_protocol = _provider->protocol_from_key(session_key);
        _active_app = app;
        _active_cipher_type = Cipher;
        _active_key_number = key_no;
    }

    template <file_type Type>
    result<file_settings<Type>> tag::get_specific_file_settings(file_id fid) {
        if (auto res_cmd = get_file_settings(fid); res_cmd) {
            // Assert the file type is correct
            if (res_cmd->type() != Type) {
                return error::malformed;
            }
            return std::move(res_cmd->template get<Type>());
        } else {
            return res_cmd.error();
        }
    }


    template <class T>
    result<> tag::write_record(file_id fid, T &&record, comm_mode operation_mode) {
        static bin_data buffer{};
        buffer.clear();
        buffer << std::forward<T>(record);
        return write_record(fid, buffer, operation_mode, 0);
    }

    template <class T>
    result<> tag::write_record(file_id fid, T &&record, trust_card_t) {
        static bin_data buffer{};
        buffer.clear();
        buffer << std::forward<T>(record);
        return write_record(fid, buffer, trust_card, 0);
    }

    template <class T>
    std::vector<T> tag::parse_records(bin_data const &data, std::uint32_t exp_count) {
        std::vector<T> records{};
        records.reserve(exp_count);
        bin_stream s{data};
        while (s.good() and (records.size() < exp_count or exp_count == all_records)) {
            records.template emplace_back();
            s >> records.back();
        }
        if (not s.eof()) {
            DESFIRE_LOGW("%s: could not parse all records, there are %u stray bytes.",
                         to_string(bits::command_code::read_records), s.remaining());
        }
        if (exp_count != all_records and records.size() != exp_count) {
            DESFIRE_LOGW("%s: expected to parse %lu records, got only %u.",
                         to_string(bits::command_code::read_records), exp_count, records.size());
        }
        return records;
    }

    template <class T>
    result<std::vector<T>> tag::read_parse_records(file_id fid, comm_mode operation_mode, std::uint32_t record_index, std::uint32_t record_count) {
        const auto res_read_records = read_records(fid, record_index, record_count, operation_mode);
        if (not res_read_records) {
            return res_read_records.error();
        }
        return parse_records<T>(*res_read_records, record_count);
    }

    template <class T>
    result<std::vector<T>> tag::read_parse_records(file_id fid, trust_card_t, std::uint32_t record_index, std::uint32_t record_count) {
        const auto res_read_records = read_records(fid, trust_card, record_index, record_count);
        if (not res_read_records) {
            return res_read_records.error();
        }
        return parse_records<T>(*res_read_records, record_count);
    }


}// namespace desfire

#endif//DESFIRE_TAG_HPP