/*
* (C) 2024 Jack Lloyd
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#include <botan/pkix_types.h>

#include <botan/ber_dec.h>
#include <botan/der_enc.h>
#include <botan/internal/int_utils.h>
#include <botan/internal/loadstor.h>
#include <botan/internal/parsing.h>

namespace Botan {

void AlternativeName::add_uri(std::string_view uri) {
   if(!uri.empty()) {
      m_uri.insert(std::string(uri));
   }
}

void AlternativeName::add_email(std::string_view addr) {
   if(!addr.empty()) {
      m_email.insert(std::string(addr));
   }
}

void AlternativeName::add_dns(std::string_view dns) {
   if(!dns.empty()) {
      m_dns.insert(tolower_string(dns));
   }
}

void AlternativeName::add_other_name(const OID& oid, const ASN1_String& value) {
   m_othernames.insert(std::make_pair(oid, value));
}

void AlternativeName::add_dn(const X509_DN& dn) {
   m_dn_names.insert(dn);
}

void AlternativeName::add_ipv4_address(uint32_t ip) {
   m_ipv4_addr.insert(ip);
}

void AlternativeName::add_ipv6_address(const IPv6Address& ip) {
   m_ipv6_addr.insert(ip);
}

size_t AlternativeName::count() const {
   const auto sum = checked_add(m_dns.size(),
                                m_uri.size(),
                                m_email.size(),
                                m_ipv4_addr.size(),
                                m_ipv6_addr.size(),
                                m_dn_names.size(),
                                m_othernames.size());

   BOTAN_ASSERT_NOMSG(sum.has_value());
   return sum.value();
}

bool AlternativeName::has_items() const {
   return this->count() > 0;
}

void AlternativeName::encode_into(DER_Encoder& der) const {
   der.start_sequence();

   /*
   GeneralName ::= CHOICE {
        otherName                       [0]     OtherName,
        rfc822Name                      [1]     IA5String,
        dNSName                         [2]     IA5String,
        x400Address                     [3]     ORAddress,
        directoryName                   [4]     Name,
        ediPartyName                    [5]     EDIPartyName,
        uniformResourceIdentifier       [6]     IA5String,
        iPAddress                       [7]     OCTET STRING,
        registeredID                    [8]     OBJECT IDENTIFIER }
   */

   for(const auto& othername : m_othernames) {
      der.start_explicit(0)
         .encode(othername.first)
         .start_explicit(0)
         .encode(othername.second)
         .end_explicit()
         .end_explicit();
   }

   for(const auto& name : m_email) {
      const ASN1_String str(name, ASN1_Type::Ia5String);
      der.add_object(ASN1_Type(1), ASN1_Class::ContextSpecific, str.value());
   }

   for(const auto& name : m_dns) {
      const ASN1_String str(name, ASN1_Type::Ia5String);
      der.add_object(ASN1_Type(2), ASN1_Class::ContextSpecific, str.value());
   }

   for(const auto& name : m_dn_names) {
      der.add_object(ASN1_Type(4), ASN1_Class::ExplicitContextSpecific, name.DER_encode());
   }

   for(const auto& name : m_uri) {
      const ASN1_String str(name, ASN1_Type::Ia5String);
      der.add_object(ASN1_Type(6), ASN1_Class::ContextSpecific, str.value());
   }

   for(const uint32_t ip : m_ipv4_addr) {
      auto ip_buf = store_be(ip);
      // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)
      der.add_object(ASN1_Type(7), ASN1_Class::ContextSpecific, ip_buf.data(), 4);
   }

   for(const auto& ip : m_ipv6_addr) {
      // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)
      der.add_object(ASN1_Type(7), ASN1_Class::ContextSpecific, ip.address().data(), ip.address().size());
   }

   der.end_cons();
}

void AlternativeName::decode_from(BER_Decoder& source) {
   BER_Decoder names = source.start_sequence();

   while(names.more_items()) {
      const BER_Object obj = names.get_next_object();

      if(obj.is_a(0, ASN1_Class::ExplicitContextSpecific)) {
         BER_Decoder othername(obj, names.limits());

         OID oid;
         othername.decode(oid);
         if(othername.more_items()) {
            const BER_Object othername_value_outer = othername.get_next_object();
            othername.verify_end();

            if(!othername_value_outer.is_a(0, ASN1_Class::ExplicitContextSpecific)) {
               throw Decoding_Error("Invalid tags on otherName value");
            }

            BER_Decoder othername_value_inner(othername_value_outer, names.limits());

            const BER_Object value = othername_value_inner.get_next_object();
            othername_value_inner.verify_end();

            if(ASN1_String::is_string_type(value.type()) && value.get_class() == ASN1_Class::Universal) {
               add_othername(oid, ASN1::to_string(value), value.type());
            }
         }
      } else if(obj.is_a(1, ASN1_Class::ContextSpecific)) {
         add_email(ASN1::to_string(obj));
      } else if(obj.is_a(2, ASN1_Class::ContextSpecific)) {
         m_dns.insert(check_and_canonicalize_dns_name(ASN1::to_string(obj)));
      } else if(obj.is_a(4, ASN1_Class::ContextSpecific | ASN1_Class::Constructed)) {
         BER_Decoder dec(obj, names.limits());
         X509_DN dn;
         dec.decode(dn);
         this->add_dn(dn);
      } else if(obj.is_a(6, ASN1_Class::ContextSpecific)) {
         this->add_uri(ASN1::to_string(obj));
      } else if(obj.is_a(7, ASN1_Class::ContextSpecific)) {
         if(obj.length() == 4) {
            const uint32_t ip = load_be<uint32_t>(obj.bits(), 0);
            this->add_ipv4_address(ip);
         } else if(obj.length() == 16) {
            const IPv6Address ip(std::span<const uint8_t, 16>{obj.bits(), 16});
            this->add_ipv6_address(ip);
         } else {
            throw Decoding_Error("Invalid IP constraint neither IPv4 or IPv6");
         }
      }
   }
}

}  // namespace Botan
