/*
 * Copyright (c) 2021 [Ribose Inc](https://www.ribose.com).
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "sec_profile.hpp"
#include "types.h"
#include "defaults.h"
#include <ctime>
#include <algorithm>

namespace rnp {
bool
SecurityRule::operator==(const SecurityRule &src) const
{
    return (type == src.type) && (feature == src.feature) && (from == src.from) &&
           (level == src.level) && (override == src.override) && (action == src.action);
}

bool
SecurityRule::operator!=(const SecurityRule &src) const
{
    return !(*this == src);
}

bool
SecurityRule::matches(FeatureType    ftype,
                      int            fval,
                      uint64_t       ftime,
                      SecurityAction faction) const noexcept
{
    if ((type != ftype) || (feature != fval) || (from > ftime)) {
        return false;
    }
    return (action == SecurityAction::Any) || (faction == SecurityAction::Any) ||
           (action == faction);
}

size_t
SecurityProfile::size() const noexcept
{
    return rules_.size();
}

SecurityRule &
SecurityProfile::add_rule(const SecurityRule &rule)
{
    rules_.push_back(rule);
    return rules_.back();
}

SecurityRule &
SecurityProfile::add_rule(SecurityRule &&rule)
{
    rules_.emplace_back(rule);
    return rules_.back();
}

bool
SecurityProfile::del_rule(const SecurityRule &rule)
{
    size_t old_size = rules_.size();
    rules_.erase(std::remove_if(rules_.begin(),
                                rules_.end(),
                                [rule](const SecurityRule &item) { return item == rule; }),
                 rules_.end());
    return old_size != rules_.size();
}

void
SecurityProfile::clear_rules(FeatureType type, int feature)
{
    rules_.erase(std::remove_if(rules_.begin(),
                                rules_.end(),
                                [type, feature](const SecurityRule &item) {
                                    return (item.type == type) && (item.feature == feature);
                                }),
                 rules_.end());
}

void
SecurityProfile::clear_rules(FeatureType type)
{
    rules_.erase(
      std::remove_if(rules_.begin(),
                     rules_.end(),
                     [type](const SecurityRule &item) { return item.type == type; }),
      rules_.end());
}

void
SecurityProfile::clear_rules()
{
    rules_.clear();
}

bool
SecurityProfile::has_rule(FeatureType    type,
                          int            value,
                          uint64_t       time,
                          SecurityAction action) const noexcept
{
    for (auto &rule : rules_) {
        if (rule.matches(type, value, time, action)) {
            return true;
        }
    }
    return false;
}

const SecurityRule &
SecurityProfile::get_rule(FeatureType    type,
                          int            value,
                          uint64_t       time,
                          SecurityAction action) const
{
    const SecurityRule *res = nullptr;
    for (auto &rule : rules_) {
        if (!rule.matches(type, value, time, action)) {
            continue;
        }
        if (rule.override) {
            return rule;
        }
        if (!res || (res->from < rule.from)) {
            res = &rule;
        }
    }
    if (!res) {
        throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
    }
    return *res;
}

SecurityLevel
SecurityProfile::hash_level(pgp_hash_alg_t hash,
                            uint64_t       time,
                            SecurityAction action) const noexcept
{
    if (!has_rule(FeatureType::Hash, hash, time, action)) {
        return def_level();
    }

    try {
        return get_rule(FeatureType::Hash, hash, time, action).level;
    } catch (const std::exception &e) {
        /* this should never happen however we need to satisfy noexcept specifier */
        return def_level();
    }
}

SecurityLevel
SecurityProfile::def_level() const
{
    return SecurityLevel::Default;
};

SecurityContext::SecurityContext() : time_(0), prov_state_(NULL), rng(RNG::Type::DRBG)
{
    /* Initialize crypto provider if needed (currently only for OpenSSL 3.0) */
    if (!rnp::backend_init(&prov_state_)) {
        throw rnp::rnp_exception(RNP_ERROR_BAD_STATE);
    }
    /* Mark SHA-1 data signature insecure since 2019-01-19, as GnuPG does */
    profile.add_rule({FeatureType::Hash,
                      PGP_HASH_SHA1,
                      SecurityLevel::Insecure,
                      1547856000,
                      SecurityAction::VerifyData});
    /* Mark SHA-1 key signature insecure since 2024-01-19 by default */
    profile.add_rule({FeatureType::Hash,
                      PGP_HASH_SHA1,
                      SecurityLevel::Insecure,
                      1705629600,
                      SecurityAction::VerifyKey});
    /* Mark MD5 insecure since 2012-01-01 */
    profile.add_rule({FeatureType::Hash, PGP_HASH_MD5, SecurityLevel::Insecure, 1325376000});
}

SecurityContext::~SecurityContext()
{
    rnp::backend_finish(prov_state_);
}

size_t
SecurityContext::s2k_iterations(pgp_hash_alg_t halg)
{
    if (!s2k_iterations_.count(halg)) {
        s2k_iterations_[halg] =
          pgp_s2k_compute_iters(halg, DEFAULT_S2K_MSEC, DEFAULT_S2K_TUNE_MSEC);
    }
    return s2k_iterations_[halg];
}

void
SecurityContext::set_time(uint64_t time) noexcept
{
    time_ = time;
}

uint64_t
SecurityContext::time() const noexcept
{
    return time_ ? time_ : ::time(NULL);
}

} // namespace rnp
