/**
 * Nudm_UEAU
 * UDM UE Authentication Service. � 2020, 3GPP Organizational Partners (ARIB,
 * ATIS, CCSA, ETSI, TSDSI, TTA, TTC). All rights reserved.
 *
 * The version of the OpenAPI document: 1.2.0-alpha.1
 *
 *
 * NOTE: This class is auto generated by OpenAPI Generator
 * (https://openapi-generator.tech). https://openapi-generator.tech Do not edit
 * the class manually.
 */

#include "GenerateAuthDataApiImpl.h"
#include "comUt.hpp"

#include <iomanip>
#include <sstream>

using namespace config;
extern udm_config udm_cfg;

namespace oai {
namespace udm {
namespace api {

using namespace oai::udm::model;

GenerateAuthDataApiImpl::GenerateAuthDataApiImpl(
    std::shared_ptr<Pistache::Rest::Router> rtr)
    : GenerateAuthDataApi(rtr) {}

void GenerateAuthDataApiImpl::generate_auth_data(
    const std::string &supiOrSuci,
    const AuthenticationInfoRequest &authenticationInfoRequest,
    Pistache::Http::ResponseWriter &response) {

  Logger::udm_ueau().info("\n\nEntering generate_auth_data()");

  // test data
  // uint8_t rand[] = {0x23, 0x55, 0x3c, 0xbe, 0x96, 0x37, 0xa8, 0x9d, 0x21,
  // 0x8a, 0xe6, 0x4d, 0xae, 0x47, 0xbf, 0x35};
  // uint8_t opc[16] = {0xcd, 0x63, 0xcb, 0x71, 0x95, 0x4a, 0x9f, 0x4e,
  //                  0x48, 0xa5, 0x99, 0x4e, 0x37, 0xa0, 0x2b, 0xaf};
  // uint8_t key[] = {0x46, 0x5b, 0x5c, 0xe8, 0xb1, 0x99, 0xb4, 0x9f,
  //                  0xaa, 0x5f, 0x0a, 0x2e, 0xe2, 0x38, 0xa6, 0xbc};
  // uint8_t sqn[6] = {0xff, 0x9b, 0xb4, 0xd0, 0xb6, 0x07};
  // uint8_t amf[2] = {0xb9, 0xb9};
  // std::string snn = "5G:mnc001.mcc460.3gppnetwork.org";
  // std::string supi = "imsi-460010123456789";

  uint8_t rand[16] = {0};
  uint8_t opc[16] = {0};
  uint8_t key[16] = {0};
  uint8_t sqn[6] = {0};
  uint8_t amf[2] = {0};

  uint8_t *r_sqn = NULL;    // for resync
  std::string r_sqnms_s;    // for resync
  uint8_t r_rand[16] = {0}; // for resync
  uint8_t r_auts[14] = {0}; // for resync

  uint8_t mac_a[8] = {0};
  uint8_t ck[16] = {0};
  uint8_t ik[16] = {0};
  uint8_t ak[6] = {0};
  uint8_t xres[8] = {0};
  uint8_t xresStar[16] = {0};
  uint8_t autn[16] = {0};
  uint8_t kausf[32] = {0};

  std::string rand_s;
  std::string autn_s;
  std::string xresStar_s;
  std::string kausf_s;
  std::string sqn_s;
  std::string amf_s;
  std::string key_s;
  std::string opc_s;

  std::string snn = authenticationInfoRequest.getServingNetworkName();
  std::string supi = supiOrSuci;

  std::string udr_ip =
      std::string(inet_ntoa(*((struct in_addr *)&udm_cfg.nudr.addr4)));
  std::string udr_port = std::to_string(udm_cfg.nudr.port);
  std::string remoteUri;
  std::string Method;
  std::string msgBody;
  std::string Response;

  nlohmann::json j_ProblemDetails;
  ProblemDetails m_ProblemDetails;

  // UDR GET interface ----- get authentication related info--------------------
  remoteUri = udr_ip + ":" + udr_port + "/nudr-dr/v2/subscription-data/" +
              supi + "/authentication-data/authentication-subscription";
  Logger::udm_ueau().debug("GET Request:" + remoteUri);
  Method = "GET";

  Curl::curl_http_client(remoteUri, Method, "", Response);

  nlohmann::json response_data = {};
  try {
    response_data = nlohmann::json::parse(Response.c_str());
  } catch (nlohmann::json::exception &e) { // error handling
    Logger::udm_ueau().info("Could not get Json content from UDR response");

    m_ProblemDetails.setCause("USER_NOT_FOUND");
    m_ProblemDetails.setStatus(404);
    m_ProblemDetails.setDetail("User " + supi + " not found in Database");
    to_json(j_ProblemDetails, m_ProblemDetails);

    Logger::udm_ueau().error("User " + supi + " not found in Database");
    Logger::udm_ueau().info("Send 404 Not_Found response to AUSF");
    response.send(Pistache::Http::Code::Not_Found, j_ProblemDetails.dump());
    return;
  }

  std::string authMethod_s = response_data.at("authenticationMethod");
  if (!authMethod_s.compare("5G_AKA") ||
      !authMethod_s.compare("AuthenticationVector")) {
    try {
      key_s = response_data.at("encPermanentKey");
      conv::hex_str_to_uint8(key_s.c_str(), key);
      // print_buffer("udm_ueau", "Result For F1-Alg: key", key , 16);

      opc_s = response_data.at("encOpcKey");
      conv::hex_str_to_uint8(opc_s.c_str(), opc);
      // print_buffer("udm_ueau", "Result For F1-Alg: opc", opc , 16);

      amf_s = response_data.at("authenticationManagementField");
      conv::hex_str_to_uint8(amf_s.c_str(), amf);
      // print_buffer("udm_ueau", "Result For F1-Alg: amf", amf , 2);

      sqn_s = response_data["sequenceNumber"].at("sqn");
      conv::hex_str_to_uint8(sqn_s.c_str(), sqn);
      // print_buffer("udm_ueau", "Result For F1-Alg: sqn", sqn , 6);
    } catch (nlohmann::json::exception &e) {
      // error handling
      m_ProblemDetails.setCause("AUTHENTICATION_REJECTED");
      m_ProblemDetails.setStatus(403);
      m_ProblemDetails.setDetail(
          "Missing authentication parameter in UDR response");
      to_json(j_ProblemDetails, m_ProblemDetails);

      Logger::udm_ueau().error(
          "Missing authentication parameter in UDR response");
      Logger::udm_ueau().info("Send 403 Forbidden response to AUSF");
      response.send(Pistache::Http::Code::Forbidden, j_ProblemDetails.dump());
      return;
    }
  } else {
    // error handling
    m_ProblemDetails.setCause("UNSUPPORTED_PROTECTION_SCHEME");
    m_ProblemDetails.setStatus(501);
    m_ProblemDetails.setDetail(
        "Non 5G_AKA authenticationMethod configuration in database");
    to_json(j_ProblemDetails, m_ProblemDetails);

    Logger::udm_ueau().error("Non 5G_AKA authenticationMethod configuration in "
                             "database, method set = " +
                             authMethod_s);
    Logger::udm_ueau().info("Send 501 Not_Implemented response to AUSF");
    response.send(Pistache::Http::Code::Not_Implemented,
                  j_ProblemDetails.dump());
    return;
  }

  if (authenticationInfoRequest.resynchronizationInfoIsSet()) {
    // resync procedure
    // ---------------------------------------------------------
    Logger::udm_ueau().info("Start resynchronization procedure");
    ResynchronizationInfo m_ResynchronizationInfo =
        authenticationInfoRequest.getResynchronizationInfo();
    std::string r_rand_s = m_ResynchronizationInfo.getRand();
    std::string r_auts_s = m_ResynchronizationInfo.getAuts();

    Logger::udm_ueau().debug("[resync] r_rand = " + r_rand_s);
    Logger::udm_ueau().debug("[resync] r_auts = " + r_auts_s);

    conv::hex_str_to_uint8(r_rand_s.c_str(), r_rand);
    conv::hex_str_to_uint8(r_auts_s.c_str(), r_auts);

    r_sqn = Authentication_5gaka::sqn_ms_derive(opc, key, r_auts, r_rand, amf);

    if (r_sqn) { // Not NULL (validate auts)
      Logger::udm_ueau().debug("Valid AUTS, generate new AV with SQNms");

      // UDR PATCH interface ------- replace SQNhe with
      // SQNms------------------------------
      remoteUri = udr_ip + ":" + udr_port + "/nudr-dr/v2/subscription-data/" +
                  supi + "/authentication-data/authentication-subscription";
      Logger::udm_ueau().debug("PATCH Request:" + remoteUri);
      Method = "PATCH";

      nlohmann::json j_SequenceNumber;
      SequenceNumber m_SequenceNumber;
      m_SequenceNumber.setSqnScheme("NON_TIME_BASED");
      r_sqnms_s = conv::uint8_to_hex_string(r_sqn, 6);
      m_SequenceNumber.setSqn(r_sqnms_s);
      std::map<std::string, int32_t> index;
      index["ausf"] = 0;
      m_SequenceNumber.setLastIndexes(index);
      to_json(j_SequenceNumber, m_SequenceNumber);

      nlohmann::json j_PatchItem;
      PatchItem m_PatchItem;
      m_PatchItem.setValue(j_SequenceNumber.dump());
      m_PatchItem.setOp("replace");
      m_PatchItem.setFrom("");
      m_PatchItem.setPath("");
      to_json(j_PatchItem, m_PatchItem);

      msgBody = "[" + j_PatchItem.dump() + "]";
      Logger::udm_ueau().debug("PATCH Request body = " + msgBody);

      Curl::curl_http_client(remoteUri, Method, msgBody, Response);

      // replace SQNhe with SQNms
      int i = 0;
      for (i; i < 6; i++)
        sqn[i] = r_sqn[i]; // generate first, increase later
      sqn_s = conv::uint8_to_hex_string(sqn, 16);
      // Logger::udm_ueau().debug("sqn string = "+sqn_s);
      sqn_s[12] = '\0';

      print_buffer("udm_ueau", "SQNms", sqn, 6);

      if (r_sqn) { // free
        free(r_sqn);
        r_sqn = NULL;
      }
    } else {
      Logger::udm_ueau().error("Invalid AUTS, generate new AV with SQNhe = " +
                               sqn_s);
    }
  }

  // 5GAKA functions---------------------------------------------------------
  Authentication_5gaka::generate_random(rand, 16);           // generate rand
  Authentication_5gaka::f1(opc, key, rand, sqn, amf, mac_a); // to compute mac_a
  Authentication_5gaka::f2345(opc, key, rand, xres, ck, ik,
                              ak); // to compute XRES, CK, IK, AK
  Authentication_5gaka::generate_autn(sqn, ak, amf, mac_a,
                                      autn); // generate AUTN
  Authentication_5gaka::annex_a_4_33501(ck, ik, xres, rand, snn,
                                        xresStar); // generate xres*
  Authentication_5gaka::derive_kausf(ck, ik, snn, sqn, ak,
                                     kausf); // derive Kausf

  // convert uint8_t to string
  rand_s = conv::uint8_to_hex_string(rand, 16);
  autn_s = conv::uint8_to_hex_string(autn, 16);
  xresStar_s = conv::uint8_to_hex_string(xresStar, 16);
  kausf_s = conv::uint8_to_hex_string(kausf, 32);

  // convert to json
  nlohmann::json AuthInfoResult = {};
  AuthInfoResult["authType"] = "5G_AKA";
  AuthInfoResult["authenticationVector"]["avType"] = "5G_HE_AKA";
  AuthInfoResult["authenticationVector"]["rand"] = rand_s;
  AuthInfoResult["authenticationVector"]["autn"] = autn_s;
  AuthInfoResult["authenticationVector"]["xresStar"] = xresStar_s;
  AuthInfoResult["authenticationVector"]["kausf"] = kausf_s;

  Logger::udm_ueau().info("Send 200 Ok response to AUSF");
  response.send(Pistache::Http::Code::Ok, AuthInfoResult.dump());

  // calculate new sqn----------------------------------------------------------
  unsigned long long sqn_value;
  std::stringstream s1;
  s1 << std::hex << sqn_s;
  s1 >> sqn_value; // hex string to decimal value
  sqn_value += 32;
  std::stringstream s2;
  s2 << std::hex << std::setw(12) << std::setfill('0')
     << sqn_value; // decimal value to hex string
  std::string new_sqn(s2.str());

  Logger::udm_ueau().debug("new_sqn = " + new_sqn);

  // UDR PATCH interface ------- increase
  // sqn------------------------------------
  remoteUri = udr_ip + ":" + udr_port + "/nudr-dr/v2/subscription-data/" +
              supi + "/authentication-data/authentication-subscription";
  Logger::udm_ueau().debug("PATCH Request:" + remoteUri);
  Method = "PATCH";

  nlohmann::json j_SequenceNumber;
  SequenceNumber m_SequenceNumber;
  m_SequenceNumber.setSqnScheme("NON_TIME_BASED");
  m_SequenceNumber.setSqn(new_sqn);
  std::map<std::string, int32_t> index;
  index["ausf"] = 0;
  m_SequenceNumber.setLastIndexes(index);
  to_json(j_SequenceNumber, m_SequenceNumber);

  nlohmann::json j_PatchItem;
  PatchItem m_PatchItem;
  m_PatchItem.setValue(j_SequenceNumber.dump());
  m_PatchItem.setOp("replace");
  m_PatchItem.setFrom("");
  m_PatchItem.setPath("");
  to_json(j_PatchItem, m_PatchItem);

  msgBody = "[" + j_PatchItem.dump() + "]";
  // Logger::udm_ueau().debug("PATCH Request body = " + msgBody);

  Curl::curl_http_client(remoteUri, Method, msgBody, Response);
  Logger::udm_ueau().info("Update sqn in Database");
}

} // namespace api
} // namespace udm
} // namespace oai
