Program Listing for File data.hpp

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

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

#ifndef DESFIRE_DATA_HPP
#define DESFIRE_DATA_HPP

#include <desfire/bits.hpp>
#include <desfire/key_actor.hpp>
#include <desfire/log.h>
#include <memory>
#include <mlab/any_of.hpp>
#include <mlab/bin_data.hpp>
#include <type_traits>

namespace desfire {
    struct app_id_tag {};

    using app_id = mlab::tagged_array<app_id_tag, bits::app_id_length>;

    static constexpr app_id root_app{0x0, 0x0, 0x0};

    using file_id = std::uint8_t;

    enum struct error : std::uint8_t {
        out_of_eeprom = static_cast<std::uint8_t>(bits::status::out_of_eeprom),
        illegal_command = static_cast<std::uint8_t>(bits::status::illegal_command),
        integrity_error = static_cast<std::uint8_t>(bits::status::integrity_error),
        no_such_key = static_cast<std::uint8_t>(bits::status::no_such_key),
        length_error = static_cast<std::uint8_t>(bits::status::length_error),
        permission_denied = static_cast<std::uint8_t>(bits::status::permission_denied),
        parameter_error = static_cast<std::uint8_t>(bits::status::parameter_error),
        app_not_found = static_cast<std::uint8_t>(bits::status::app_not_found),
        app_integrity_error = static_cast<std::uint8_t>(bits::status::app_integrity_error),
        authentication_error = static_cast<std::uint8_t>(bits::status::authentication_error),
        boundary_error = static_cast<std::uint8_t>(bits::status::boundary_error),
        picc_integrity_error = static_cast<std::uint8_t>(bits::status::picc_integrity_error),
        command_aborted = static_cast<std::uint8_t>(bits::status::command_aborted),
        picc_disabled_error = static_cast<std::uint8_t>(bits::status::picc_disabled_error),
        count_error = static_cast<std::uint8_t>(bits::status::count_error),
        duplicate_error = static_cast<std::uint8_t>(bits::status::duplicate_error),
        eeprom_error = static_cast<std::uint8_t>(bits::status::eeprom_error),
        file_not_found = static_cast<std::uint8_t>(bits::status::file_not_found),
        file_integrity_error = static_cast<std::uint8_t>(bits::status::file_integrity_error),
        controller_error,
        malformed,
        crypto_error
    };

    [[nodiscard]] error error_from_status(bits::status s);

    struct same_key_t {};

    static constexpr same_key_t same_key{};

    struct key_rights {
        key_actor<same_key_t> allowed_to_change_keys{same_key};

        bool master_key_changeable = true;

        bool dir_access_without_auth = true;

        bool create_delete_without_master_key = false;

        bool config_changeable = true;

        [[nodiscard]] inline bool operator==(desfire::key_rights const &other) const;
        [[nodiscard]] inline bool operator!=(desfire::key_rights const &other) const;
        constexpr key_rights() = default;

        constexpr key_rights(key_actor<same_key_t> allowed_to_change_keys_,
                             bool master_key_changeable_,
                             bool dir_access_without_auth_,
                             bool create_delete_without_master_key_,
                             bool config_changeable_)
            : allowed_to_change_keys{allowed_to_change_keys_},
              master_key_changeable{master_key_changeable_},
              dir_access_without_auth{dir_access_without_auth_},
              create_delete_without_master_key{create_delete_without_master_key_},
              config_changeable{config_changeable_} {}
    };

    struct free_access_t {};

    static constexpr free_access_t free_access{};

    enum struct file_access {
        change,
        read,
        write
    };

    struct file_access_rights {
        key_actor<free_access_t> change;
        key_actor<free_access_t> read_write;
        key_actor<free_access_t> write;
        key_actor<free_access_t> read;

        constexpr file_access_rights() = default;

        constexpr file_access_rights(no_key_t);

        constexpr file_access_rights(free_access_t);

        constexpr explicit file_access_rights(std::uint8_t single_key);

        explicit file_access_rights(bool) = delete;

        constexpr file_access_rights(key_actor<free_access_t> rw, key_actor<free_access_t> chg);

        constexpr file_access_rights(key_actor<free_access_t> rw, key_actor<free_access_t> chg, key_actor<free_access_t> r, key_actor<free_access_t> w);

        inline void set_word(std::uint16_t v);

        [[nodiscard]] inline std::uint16_t get_word() const;

        [[nodiscard]] inline static file_access_rights from_word(std::uint16_t word);

        [[nodiscard]] bool is_free(file_access access) const;

        [[nodiscard]] inline bool operator==(file_access_rights const &other) const;
        [[nodiscard]] inline bool operator!=(file_access_rights const &other) const;
    };

    struct common_file_settings {
        file_security security = file_security::none;
        file_access_rights rights;

        constexpr common_file_settings() = default;
        constexpr common_file_settings(file_security security_, file_access_rights rights_);
    };

    struct data_file_settings {
        std::uint32_t size = 0;

        constexpr data_file_settings() = default;
        explicit constexpr data_file_settings(std::uint32_t size_) : size{size_} {}
    };

    struct value_file_settings {
        std::int32_t lower_limit = 0;
        std::int32_t upper_limit = 0;
        std::int32_t value = 0;

        bool limited_credit_enabled = false;

        constexpr value_file_settings() = default;

        constexpr value_file_settings(std::int32_t lowlim, std::int32_t uplim, std::int32_t v, bool enable_lim_credit = false)
            : lower_limit{lowlim},
              upper_limit{uplim},
              value{v},
              limited_credit_enabled{enable_lim_credit} {}
    };

    struct record_file_settings {
        std::uint32_t record_size = 0;

        std::uint32_t max_record_count = 0;

        std::uint32_t record_count = 0;

        constexpr record_file_settings() = default;

        constexpr record_file_settings(std::uint32_t rec_size, std::uint32_t max_rec_count, std::uint32_t rec_count = 0)
            : record_size{rec_size},
              max_record_count{max_rec_count},
              record_count{rec_count} {}
    };

    template <file_type>
    struct file_settings {};

    template <>
    struct file_settings<file_type::standard> : public common_file_settings, public data_file_settings {
        using specific_file_settings = data_file_settings;

        constexpr file_settings() : common_file_settings{}, data_file_settings{} {}

        constexpr file_settings(common_file_settings generic, data_file_settings specific)
            : common_file_settings{generic}, data_file_settings{specific} {}

        constexpr file_settings(file_security security, file_access_rights rights, std::uint32_t size)
            : common_file_settings{security, rights}, data_file_settings{size} {}

        constexpr file_settings(common_file_settings generic, std::uint32_t size)
            : common_file_settings{generic}, data_file_settings{size} {}

        constexpr file_settings(file_security security, file_access_rights rights, data_file_settings specific)
            : common_file_settings{security, rights}, data_file_settings{specific} {}
    };

    template <>
    struct file_settings<file_type::backup> : public common_file_settings, public data_file_settings {
        using specific_file_settings = data_file_settings;

        constexpr file_settings() : common_file_settings{}, data_file_settings{} {}

        constexpr file_settings(common_file_settings generic, data_file_settings specific)
            : common_file_settings{generic}, data_file_settings{specific} {}

        constexpr file_settings(file_security security, file_access_rights rights, std::uint32_t size)
            : common_file_settings{security, rights}, data_file_settings{size} {}

        constexpr file_settings(common_file_settings generic, std::uint32_t size)
            : common_file_settings{generic}, data_file_settings{size} {}

        constexpr file_settings(file_security security, file_access_rights rights, data_file_settings specific)
            : common_file_settings{security, rights}, data_file_settings{specific} {}
    };

    template <>
    struct file_settings<file_type::value> : public common_file_settings, public value_file_settings {
        using specific_file_settings = value_file_settings;
        constexpr file_settings()
            : common_file_settings{},
              value_file_settings{0, 0, 0, false} {}

        constexpr file_settings(common_file_settings generic, value_file_settings specific)
            : common_file_settings{generic}, value_file_settings{specific} {}

        constexpr file_settings(file_security security, file_access_rights rights,
                                std::int32_t lowlim, std::int32_t uplim, std::int32_t v, bool enable_lim_credit = false)
            : common_file_settings{security, rights},
              value_file_settings{lowlim, uplim, v, enable_lim_credit} {}

        constexpr file_settings(common_file_settings generic,
                                std::int32_t lowlim, std::int32_t uplim, std::int32_t v, bool enable_lim_credit = false)
            : common_file_settings{generic},
              value_file_settings{lowlim, uplim, v, enable_lim_credit} {}

        constexpr file_settings(file_security security, file_access_rights rights,
                                value_file_settings specific)
            : common_file_settings{security, rights},
              value_file_settings{specific} {}
    };

    template <>
    struct file_settings<file_type::linear_record> : public common_file_settings, public record_file_settings {
        using specific_file_settings = record_file_settings;

        constexpr file_settings()
            : common_file_settings{},
              record_file_settings{0, 0, 0} {}

        constexpr file_settings(common_file_settings generic, record_file_settings specific)
            : common_file_settings{generic}, record_file_settings{specific} {}

        constexpr file_settings(file_security security, file_access_rights rights,
                                std::uint32_t rec_size, std::uint32_t max_rec_count, std::uint32_t rec_count = 0)
            : common_file_settings{security, rights},
              record_file_settings{rec_size, max_rec_count, rec_count} {}


        constexpr file_settings(common_file_settings generic,
                                std::uint32_t rec_size, std::uint32_t max_rec_count, std::uint32_t rec_count = 0)
            : common_file_settings{generic},
              record_file_settings{rec_size, max_rec_count, rec_count} {}

        constexpr file_settings(file_security security, file_access_rights rights,
                                record_file_settings specific)
            : common_file_settings{security, rights},
              record_file_settings{specific} {}
    };

    template <>
    struct file_settings<file_type::cyclic_record> : public common_file_settings, public record_file_settings {
        using specific_file_settings = record_file_settings;

        constexpr file_settings()
            : common_file_settings{},
              record_file_settings{0, 0, 0} {}

        constexpr file_settings(common_file_settings generic, record_file_settings specific)
            : common_file_settings{generic}, record_file_settings{specific} {}

        constexpr file_settings(file_security security, file_access_rights rights,
                                std::uint32_t rec_size, std::uint32_t max_rec_count, std::uint32_t rec_count = 0)
            : common_file_settings{security, rights},
              record_file_settings{rec_size, max_rec_count, rec_count} {}

        constexpr file_settings(common_file_settings generic,
                                std::uint32_t rec_size, std::uint32_t max_rec_count, std::uint32_t rec_count = 0)
            : common_file_settings{generic},
              record_file_settings{rec_size, max_rec_count, rec_count} {}


        constexpr file_settings(file_security security, file_access_rights rights,
                                record_file_settings specific)
            : common_file_settings{security, rights},
              record_file_settings{specific} {}
    };


    class any_file_settings : public mlab::any_of<file_type, file_settings, file_type::standard> {
    public:
        using mlab::any_of<file_type, file_settings, file_type::standard>::any_of;

        [[nodiscard]] common_file_settings const &common_settings() const;

        [[nodiscard]] data_file_settings const &data_settings() const;

        [[nodiscard]] record_file_settings const &record_settings() const;

        [[nodiscard]] value_file_settings const &value_settings() const;

        [[nodiscard]] common_file_settings &common_settings();

        [[nodiscard]] data_file_settings &data_settings();

        [[nodiscard]] record_file_settings &record_settings();

        [[nodiscard]] value_file_settings &value_settings();
    };
    struct app_settings {
        key_rights rights;
        std::uint8_t max_num_keys;
        app_crypto crypto;

        constexpr explicit app_settings(app_crypto crypto_ = app_crypto::legacy_des_2k3des,
                                        key_rights rights_ = key_rights{},
                                        std::uint8_t max_num_keys_ = bits::max_keys_per_app);

        constexpr explicit app_settings(cipher_type cipher,
                                        key_rights rights_ = key_rights{},
                                        std::uint8_t max_num_keys_ = bits::max_keys_per_app);
    };

    class storage_size {
        std::uint8_t _flag;

        [[nodiscard]] inline unsigned exponent() const;
        [[nodiscard]] inline bool approx() const;

    public:
        explicit storage_size(std::size_t nbytes = 0);

        [[nodiscard]] inline std::size_t bytes_lower_bound() const;

        [[nodiscard]] inline std::size_t bytes_upper_bound() const;

        mlab::bin_stream &operator>>(mlab::bin_stream &s);
        mlab::bin_data &operator<<(mlab::bin_data &s) const;
    };

    struct ware_info {
        std::uint8_t vendor_id = 0;
        std::uint8_t type = 0;
        std::uint8_t subtype = 0;
        std::uint8_t version_major = 0;
        std::uint8_t version_minor = 0;
        storage_size size;
        std::uint8_t comm_protocol_type = 0;
    };

    struct manufacturing_info {
        ware_info hardware;
        ware_info software;
        std::array<std::uint8_t, 7> serial_no{};
        std::array<std::uint8_t, 5> batch_no{};

        std::uint8_t production_week = 0;

        std::uint8_t production_year = 0;
    };

}// namespace desfire

namespace mlab {
#ifndef DOXYGEN_SHOULD_SKIP_THIS
    bin_stream &operator>>(bin_stream &s, desfire::key_rights &kr);
    bin_stream &operator>>(bin_stream &s, desfire::app_settings &ks);
    bin_stream &operator>>(bin_stream &s, std::vector<desfire::app_id> &ids);
    bin_stream &operator>>(bin_stream &s, desfire::ware_info &wi);
    bin_stream &operator>>(bin_stream &s, desfire::manufacturing_info &mi);

    bin_stream &operator>>(bin_stream &s, desfire::file_access_rights &ar);
    bin_stream &operator>>(bin_stream &s, desfire::common_file_settings &fs);
    bin_stream &operator>>(bin_stream &s, desfire::data_file_settings &fs);
    bin_stream &operator>>(bin_stream &s, desfire::value_file_settings &fs);
    bin_stream &operator>>(bin_stream &s, desfire::record_file_settings &fs);
    bin_stream &operator>>(bin_stream &s, desfire::any_file_settings &fs);

    template <desfire::file_type Type>
    bin_stream &operator>>(bin_stream &s, desfire::file_settings<Type> &fs);

    bin_data &operator<<(bin_data &bd, desfire::key_rights const &kr);
    bin_data &operator<<(bin_data &bd, desfire::app_settings const &ks);

    bin_data &operator<<(bin_data &bd, desfire::file_access_rights const &ar);
    bin_data &operator<<(bin_data &bd, desfire::common_file_settings const &fs);
    bin_data &operator<<(bin_data &bd, desfire::data_file_settings const &fs);
    bin_data &operator<<(bin_data &bd, desfire::value_file_settings const &fs);
    bin_data &operator<<(bin_data &bd, desfire::record_file_settings const &fs);
    bin_data &operator<<(bin_data &bd, desfire::any_file_settings const &fs);

    template <desfire::file_type Type>
    bin_data &operator<<(bin_data &bd, desfire::file_settings<Type> const &fs);
#endif
}// namespace mlab

namespace desfire {

    constexpr app_settings::app_settings(app_crypto crypto_, key_rights rights_, std::uint8_t max_num_keys_) : rights{rights_}, max_num_keys{max_num_keys_}, crypto{crypto_} {}

    constexpr app_settings::app_settings(cipher_type cipher, key_rights rights_, std::uint8_t max_num_keys_) : rights{rights_}, max_num_keys{max_num_keys_}, crypto{app_crypto_from_cipher(cipher)} {}

    unsigned storage_size::exponent() const {
        return _flag >> bits::storage_size_exponent_shift;
    }
    bool storage_size::approx() const {
        return 0 != (_flag & bits::storage_size_approx_bit);
    }
    std::size_t storage_size::bytes_lower_bound() const {
        return 1 << exponent();
    }
    std::size_t storage_size::bytes_upper_bound() const {
        return 1 << (approx() ? exponent() + 1 : exponent());
    }

    constexpr file_access_rights::file_access_rights(std::uint8_t single_key) : change{single_key}, read_write{single_key}, write{single_key}, read{single_key} {
        // TODO: when C++20 is enabled, used is_constant_evaluated to issue a warning if single_key is out of range
    }

    constexpr file_access_rights::file_access_rights(no_key_t) : change{no_key}, read_write{no_key}, write{no_key}, read{no_key} {}

    constexpr file_access_rights::file_access_rights(free_access_t) : change{free_access}, read_write{free_access}, write{free_access}, read{free_access} {}

    constexpr file_access_rights::file_access_rights(key_actor<free_access_t> rw, key_actor<free_access_t> chg) : file_access_rights{no_key} {
        read_write = rw;
        change = chg;
    }

    constexpr file_access_rights::file_access_rights(key_actor<free_access_t> rw, key_actor<free_access_t> chg, key_actor<free_access_t> r, key_actor<free_access_t> w)
        : file_access_rights{no_key} {
        read_write = rw;
        change = chg;
        read = r;
        write = w;
    }

    std::uint16_t file_access_rights::get_word() const {
        return (std::uint16_t(read_write.get_nibble()) << bits::file_access_rights_read_write_shift) |
               (std::uint16_t(change.get_nibble()) << bits::file_access_rights_change_shift) |
               (std::uint16_t(read.get_nibble()) << bits::file_access_rights_read_shift) |
               (std::uint16_t(write.get_nibble()) << bits::file_access_rights_write_shift);
    }

    void file_access_rights::set_word(std::uint16_t v) {
        read_write.set_nibble(std::uint8_t((v >> bits::file_access_rights_read_write_shift) & 0b1111));
        change.set_nibble(std::uint8_t((v >> bits::file_access_rights_change_shift) & 0b1111));
        read.set_nibble(std::uint8_t((v >> bits::file_access_rights_read_shift) & 0b1111));
        write.set_nibble(std::uint8_t((v >> bits::file_access_rights_write_shift) & 0b1111));
    }

    file_access_rights file_access_rights::from_word(std::uint16_t word) {
        file_access_rights retval;
        retval.set_word(word);
        return retval;
    }

    bool file_access_rights::operator==(file_access_rights const &other) const {
        return change == other.change and
               read_write == other.read_write and
               write == other.write and
               read == other.read;
    }

    bool file_access_rights::operator!=(file_access_rights const &other) const {
        return change != other.change or
               read_write != other.read_write or
               write != other.write or
               read != other.read;
    }


    constexpr common_file_settings::common_file_settings(file_security security_, file_access_rights rights_) : security{security_}, rights{rights_} {}


    bool desfire::key_rights::operator==(desfire::key_rights const &other) const {
        return other.allowed_to_change_keys == allowed_to_change_keys and
               other.create_delete_without_master_key == create_delete_without_master_key and
               other.dir_access_without_auth == dir_access_without_auth and
               other.config_changeable == config_changeable and
               other.master_key_changeable == master_key_changeable;
    }

    bool desfire::key_rights::operator!=(desfire::key_rights const &other) const {
        return not operator==(other);
    }
}// namespace desfire

namespace mlab {

    template <desfire::file_type Type>
    bin_stream &operator>>(bin_stream &s, desfire::file_settings<Type> &fs) {
        if (not s.bad()) {
            s >> static_cast<desfire::common_file_settings &>(fs);
        }
        if (not s.bad()) {
            s >> static_cast<typename desfire::file_settings<Type>::specific_file_settings &>(fs);
        }
        return s;
    }

    template <desfire::file_type Type>
    bin_data &operator<<(bin_data &bd, desfire::file_settings<Type> const &fs) {
        return bd
               << static_cast<desfire::common_file_settings const &>(fs)
               << static_cast<typename desfire::file_settings<Type>::specific_file_settings const &>(fs);
    }
}// namespace mlab

#endif//DESFIRE_DATA_HPP