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