/*
 * Copyright (C) 2019 by Sukchan Lee <acetcom@gmail.com>
 *
 * This file is part of Open5GS.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include "ogs-ngap.h"
#include "core/abts.h"

static void ngap_message_test1(abts_case *tc, void *data)
{
    ogs_pkbuf_t *pkbuf = NULL;
    NGAP_NGAP_PDU_t pdu;
    NGAP_SuccessfulOutcome_t *successfulOutcome = NULL;
    NGAP_NGResetAcknowledge_t *NGResetAcknowledge = NULL;

    NGAP_NGResetAcknowledgeIEs_t *ie = NULL;
    NGAP_UE_associatedLogicalNG_connectionList_t
        *UE_associatedLogicalNG_connectionList = NULL;

    ogs_ngap_message_t message, *struct_ptr = NULL;
    size_t struct_size;
    asn_dec_rval_t dec_ret = {0};

    uint64_t ran_ue_ngap_id;
    uint64_t amf_ue_ngap_id;

    memset(&pdu, 0, sizeof (NGAP_NGAP_PDU_t));
    pdu.present = NGAP_NGAP_PDU_PR_successfulOutcome;
    pdu.choice.successfulOutcome = CALLOC(1, sizeof(NGAP_SuccessfulOutcome_t));

    successfulOutcome = pdu.choice.successfulOutcome;
    successfulOutcome->procedureCode = NGAP_ProcedureCode_id_NGReset;
    successfulOutcome->criticality = NGAP_Criticality_reject;
    successfulOutcome->value.present =
        NGAP_SuccessfulOutcome__value_PR_NGResetAcknowledge;

    NGResetAcknowledge = CALLOC(1, sizeof(*NGResetAcknowledge));
    ogs_assert(NGResetAcknowledge);
    successfulOutcome->value.choice.NGResetAcknowledge = NGResetAcknowledge;

    NGResetAcknowledge->protocolIEs =
        ogs_asn_calloc_protocol_ies(&asn_DEF_NGAP_NGResetAcknowledge);
    ogs_assert(NGResetAcknowledge->protocolIEs);

    ie = CALLOC(1, sizeof(NGAP_NGResetAcknowledgeIEs_t));
    ogs_assert(ie);
    ASN_SEQUENCE_ADD(NGResetAcknowledge->protocolIEs, ie);

    ie->id = NGAP_ProtocolIE_ID_id_UE_associatedLogicalNG_connectionList;
    ie->criticality = NGAP_Criticality_reject;
    ie->value.present = NGAP_NGResetAcknowledgeIEs__value_PR_UE_associatedLogicalNG_connectionList;

    UE_associatedLogicalNG_connectionList =
        CALLOC(1, sizeof(*UE_associatedLogicalNG_connectionList));
    ogs_assert(UE_associatedLogicalNG_connectionList);
    ie->value.choice.UE_associatedLogicalNG_connectionList =
        UE_associatedLogicalNG_connectionList;

    ran_ue_ngap_id = 1;
    amf_ue_ngap_id = 2;

    ogs_ngap_build_part_of_ng_interface(UE_associatedLogicalNG_connectionList,
            &ran_ue_ngap_id, &amf_ue_ngap_id);

    pkbuf = ogs_ngap_encode(&pdu);
    ogs_assert(pkbuf);

    struct_ptr = &message;
    struct_size = sizeof(ogs_ngap_message_t);

    memset(struct_ptr, 0, struct_size);
    dec_ret = aper_decode(NULL, &asn_DEF_NGAP_NGAP_PDU, (void **)&struct_ptr,
            pkbuf->data, pkbuf->len, 0, 0);
    ABTS_INT_EQUAL(tc, 0, dec_ret.code);
    ABTS_INT_EQUAL(tc, 128, dec_ret.consumed);

    ogs_ngap_free(&message);
    ogs_pkbuf_free(pkbuf);
}

static void ngap_message_test2(abts_case *tc, void *data)
{
    /* NGReset */
    const char *payload = "0014001300000200 0f400200c0005800 06400160010001";

    ogs_ngap_message_t message;
    ogs_pkbuf_t *pkbuf;
    int result;
    char hexbuf[OGS_HUGE_LEN];

    ogs_ngap_message_t *struct_ptr = NULL;
    size_t struct_size;
    asn_dec_rval_t dec_ret = {0};

    pkbuf = ogs_pkbuf_alloc(NULL, OGS_MAX_SDU_LEN);
    ogs_assert(pkbuf);
    ogs_pkbuf_put_data(pkbuf,
            ogs_hex_from_string(payload, hexbuf, sizeof(hexbuf)), 23);

    struct_ptr = &message;
    struct_size = sizeof(ogs_ngap_message_t);

    memset(struct_ptr, 0, struct_size);
    dec_ret = aper_decode(NULL, &asn_DEF_NGAP_NGAP_PDU, (void **)&struct_ptr,
            pkbuf->data, pkbuf->len, 0, 0);
    ABTS_INT_EQUAL(tc, 0, dec_ret.code);
    ABTS_INT_EQUAL(tc, 184, dec_ret.consumed);

    ogs_ngap_free(&message);
    ogs_pkbuf_free(pkbuf);
}

static void ngap_message_test3(abts_case *tc, void *data)
{
    /* NGReset */
    const char *payload =
        "0015004200000500 1b00090009f10728 000800000052400b 0400354720674e42"
        "2d43550066000d00 000000010009f107 0000000800154001 0001114009403035"
        "484c41423032";

    ogs_ngap_message_t message;
    ogs_pkbuf_t *pkbuf;
    int result;
    char hexbuf[OGS_HUGE_LEN];

    ogs_ngap_message_t *struct_ptr = NULL;
    size_t struct_size;
    asn_dec_rval_t dec_ret = {0};

    pkbuf = ogs_pkbuf_alloc(NULL, OGS_MAX_SDU_LEN);
    ogs_assert(pkbuf);
    ogs_pkbuf_put_data(pkbuf,
            ogs_hex_from_string(payload, hexbuf, sizeof(hexbuf)), 70);

    struct_ptr = &message;
    struct_size = sizeof(ogs_ngap_message_t);

    memset(struct_ptr, 0, struct_size);
    dec_ret = aper_decode(NULL, &asn_DEF_NGAP_NGAP_PDU, (void **)&struct_ptr,
            pkbuf->data, pkbuf->len, 0, 0);
    ABTS_INT_EQUAL(tc, 0, dec_ret.code);
    ABTS_INT_EQUAL(tc, 560, dec_ret.consumed);

    ogs_ngap_free(&message);
    ogs_pkbuf_free(pkbuf);
}

static void ngap_message_test4(abts_case *tc, void *data)
{
    /* NGReset */
    const char *payload =
        "0015003f00000500 1b00080045f01000 0000040052400903 0035484c41423032"
        "0066000d00000062 280045f010000000 0800154001400111 4009203035484c41"
        "423032";

    ogs_ngap_message_t message;
    ogs_pkbuf_t *pkbuf;
    int result;
    char hexbuf[OGS_HUGE_LEN];

    ogs_ngap_message_t *struct_ptr = NULL;
    size_t struct_size;
    asn_dec_rval_t dec_ret = {0};

    pkbuf = ogs_pkbuf_alloc(NULL, OGS_MAX_SDU_LEN);
    ogs_assert(pkbuf);
    ogs_pkbuf_put_data(pkbuf,
            ogs_hex_from_string(payload, hexbuf, sizeof(hexbuf)), 67);

    struct_ptr = &message;
    struct_size = sizeof(ogs_ngap_message_t);

    memset(struct_ptr, 0, struct_size);
    dec_ret = aper_decode(NULL, &asn_DEF_NGAP_NGAP_PDU, (void **)&struct_ptr,
            pkbuf->data, pkbuf->len, 0, 0);
    ABTS_INT_EQUAL(tc, 0, dec_ret.code);
    ABTS_INT_EQUAL(tc, 536, dec_ret.consumed);

    ogs_ngap_free(&message);
    ogs_pkbuf_free(pkbuf);
}

static ogs_pkbuf_t *build_uplink_nas_transport(
        uint64_t ran_ue_ngap_id, uint64_t amf_ue_ngap_id, ogs_pkbuf_t *gmmbuf)
{
    NGAP_NGAP_PDU_t pdu;
    NGAP_InitiatingMessage_t *initiatingMessage = NULL;
    NGAP_UplinkNASTransport_t *UplinkNASTransport = NULL;

    NGAP_UplinkNASTransport_IEs_t *ie = NULL;
    NGAP_AMF_UE_NGAP_ID_t *AMF_UE_NGAP_ID = NULL;
    NGAP_RAN_UE_NGAP_ID_t *RAN_UE_NGAP_ID = NULL;
    NGAP_NAS_PDU_t *NAS_PDU = NULL;
    NGAP_UserLocationInformation_t *UserLocationInformation = NULL;
    NGAP_UserLocationInformationNR_t *userLocationInformationNR = NULL;
    NGAP_NR_CGI_t *nR_CGI = NULL;
    NGAP_TAI_t *tAI = NULL;

    ogs_nr_cgi_t nr_cgi;
    ogs_5gs_tai_t nr_tai;

    ogs_assert(gmmbuf);

    memset(&nr_cgi, 0, sizeof(nr_cgi));
    memset(&nr_tai, 0, sizeof(nr_tai));

    memset(&pdu, 0, sizeof (NGAP_NGAP_PDU_t));
    pdu.present = NGAP_NGAP_PDU_PR_initiatingMessage;
    pdu.choice.initiatingMessage =
        CALLOC(1, sizeof(NGAP_InitiatingMessage_t));

    initiatingMessage = pdu.choice.initiatingMessage;
    initiatingMessage->procedureCode =
        NGAP_ProcedureCode_id_UplinkNASTransport;
    initiatingMessage->criticality = NGAP_Criticality_ignore;
    initiatingMessage->value.present =
        NGAP_InitiatingMessage__value_PR_UplinkNASTransport;

    UplinkNASTransport = CALLOC(1, sizeof(*UplinkNASTransport));
    ogs_assert(UplinkNASTransport);
    initiatingMessage->value.choice.UplinkNASTransport = UplinkNASTransport;

    UplinkNASTransport->protocolIEs =
        ogs_asn_calloc_protocol_ies(&asn_DEF_NGAP_UplinkNASTransport);
    ogs_assert(UplinkNASTransport->protocolIEs);

    ie = CALLOC(1, sizeof(NGAP_UplinkNASTransport_IEs_t));
    ogs_assert(ie);
    ASN_SEQUENCE_ADD(UplinkNASTransport->protocolIEs, ie);

    ie->id = NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID;
    ie->criticality = NGAP_Criticality_reject;
    ie->value.present = NGAP_UplinkNASTransport_IEs__value_PR_AMF_UE_NGAP_ID;

    AMF_UE_NGAP_ID = CALLOC(1, sizeof(*AMF_UE_NGAP_ID));
    ogs_assert(AMF_UE_NGAP_ID);
    ie->value.choice.AMF_UE_NGAP_ID = AMF_UE_NGAP_ID;

    ie = CALLOC(1, sizeof(NGAP_UplinkNASTransport_IEs_t));
    ogs_assert(ie);
    ASN_SEQUENCE_ADD(UplinkNASTransport->protocolIEs, ie);

    ie->id = NGAP_ProtocolIE_ID_id_RAN_UE_NGAP_ID;
    ie->criticality = NGAP_Criticality_reject;
    ie->value.present = NGAP_UplinkNASTransport_IEs__value_PR_RAN_UE_NGAP_ID;

    RAN_UE_NGAP_ID = CALLOC(1, sizeof(*RAN_UE_NGAP_ID));
    ogs_assert(RAN_UE_NGAP_ID);
    ie->value.choice.RAN_UE_NGAP_ID = RAN_UE_NGAP_ID;

    ie = CALLOC(1, sizeof(NGAP_UplinkNASTransport_IEs_t));
    ogs_assert(ie);
    ASN_SEQUENCE_ADD(UplinkNASTransport->protocolIEs, ie);

    ie->id = NGAP_ProtocolIE_ID_id_NAS_PDU;
    ie->criticality = NGAP_Criticality_reject;
    ie->value.present = NGAP_UplinkNASTransport_IEs__value_PR_NAS_PDU;

    NAS_PDU = CALLOC(1, sizeof(*NAS_PDU));
    ogs_assert(NAS_PDU);
    ie->value.choice.NAS_PDU = NAS_PDU;

    ie = CALLOC(1, sizeof(NGAP_UplinkNASTransport_IEs_t));
    ogs_assert(ie);
    ASN_SEQUENCE_ADD(UplinkNASTransport->protocolIEs, ie);

    ie->id = NGAP_ProtocolIE_ID_id_UserLocationInformation;
    ie->criticality = NGAP_Criticality_ignore;
    ie->value.present =
        NGAP_UplinkNASTransport_IEs__value_PR_UserLocationInformation;

    UserLocationInformation = CALLOC(1, sizeof(*UserLocationInformation));
    ogs_assert(UserLocationInformation);
    ie->value.choice.UserLocationInformation = UserLocationInformation;

    asn_uint642INTEGER(AMF_UE_NGAP_ID, amf_ue_ngap_id);
    *RAN_UE_NGAP_ID = ran_ue_ngap_id;

    NAS_PDU->size = gmmbuf->len;
    NAS_PDU->buf = CALLOC(NAS_PDU->size, sizeof(uint8_t));
    memcpy(NAS_PDU->buf, gmmbuf->data, NAS_PDU->size);
    ogs_pkbuf_free(gmmbuf);

    userLocationInformationNR =
            CALLOC(1, sizeof(*userLocationInformationNR));
    ogs_assert(userLocationInformationNR);

    nR_CGI = CALLOC(1, sizeof(*nR_CGI));
    ogs_assert(nR_CGI);
    userLocationInformationNR->nR_CGI = nR_CGI;
    ogs_ngap_nr_cgi_to_ASN(&nr_cgi, nR_CGI);

    tAI = CALLOC(1, sizeof(*tAI));
    ogs_assert(tAI);
    userLocationInformationNR->tAI = tAI;
    ogs_ngap_5gs_tai_to_ASN(&nr_tai, tAI);

    UserLocationInformation->present =
        NGAP_UserLocationInformation_PR_userLocationInformationNR;
    UserLocationInformation->choice.userLocationInformationNR =
        userLocationInformationNR;

    return ogs_ngap_encode(&pdu);
}

static void ngap_message_test5_issues2934(abts_case *tc, void *data)
{
    const char *payload =
        "7e005c00 0d0199f9 07f0ff00 00000020"
        "3190";
    ogs_pkbuf_t *gmmbuf = NULL;
    ogs_pkbuf_t *ngapbuf = NULL;
    char hexbuf[OGS_HUGE_LEN];

    ogs_ngap_message_t message, *struct_ptr = NULL;
    size_t struct_size;
    asn_dec_rval_t dec_ret = {0};

    int i;
    NGAP_InitiatingMessage_t *initiatingMessage = NULL;
    NGAP_UplinkNASTransport_t *UplinkNASTransport = NULL;

    NGAP_UplinkNASTransport_IEs_t *ie = NULL;
    NGAP_AMF_UE_NGAP_ID_t *AMF_UE_NGAP_ID = NULL;
    NGAP_RAN_UE_NGAP_ID_t *RAN_UE_NGAP_ID = NULL;
    NGAP_NAS_PDU_t *NAS_PDU = NULL;

    uint64_t amf_ue_ngap_id;

#define NGAPBUF_SIZE 18

    gmmbuf = ogs_pkbuf_alloc(NULL, OGS_MAX_SDU_LEN);
    ogs_assert(gmmbuf);
    ogs_pkbuf_put_data(gmmbuf,
            ogs_hex_from_string(payload, hexbuf, sizeof(hexbuf)), NGAPBUF_SIZE);

    ngapbuf = build_uplink_nas_transport(1, 0xffffffff, gmmbuf);
    ABTS_PTR_NOTNULL(tc, ngapbuf);
    ogs_pkbuf_free(ngapbuf);

    gmmbuf = ogs_pkbuf_alloc(NULL, OGS_MAX_SDU_LEN);
    ogs_assert(gmmbuf);
    ogs_pkbuf_put_data(gmmbuf,
            ogs_hex_from_string(payload, hexbuf, sizeof(hexbuf)), NGAPBUF_SIZE);

    ngapbuf = build_uplink_nas_transport(1, 0xffffffffff, gmmbuf);
    ABTS_PTR_NOTNULL(tc, ngapbuf);

    struct_ptr = &message;
    struct_size = sizeof(ogs_ngap_message_t);

    memset(struct_ptr, 0, struct_size);
    dec_ret = aper_decode(NULL, &asn_DEF_NGAP_NGAP_PDU, (void **)&struct_ptr,
            ngapbuf->data, ngapbuf->len, 0, 0);
    ABTS_INT_EQUAL(tc, 0, dec_ret.code);
    ABTS_INT_EQUAL(tc, ngapbuf->len * 8, dec_ret.consumed);

    initiatingMessage = message.choice.initiatingMessage;
    UplinkNASTransport = initiatingMessage->value.choice.UplinkNASTransport;

    for (i = 0; i < OGS_ASN_LIST_COUNT(UplinkNASTransport->protocolIEs); i++) {
        ie = OGS_ASN_LIST_GET(UplinkNASTransport->protocolIEs, i);
        switch (ie->id) {
        case NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID:
            AMF_UE_NGAP_ID = ie->value.choice.AMF_UE_NGAP_ID;
            break;
        case NGAP_ProtocolIE_ID_id_RAN_UE_NGAP_ID:
            RAN_UE_NGAP_ID = ie->value.choice.RAN_UE_NGAP_ID;
            break;
        case NGAP_ProtocolIE_ID_id_NAS_PDU:
            NAS_PDU = ie->value.choice.NAS_PDU;
            break;
        default:
            break;
        }
    }

    ABTS_PTR_NOTNULL(tc, AMF_UE_NGAP_ID);
    asn_INTEGER2uint64(AMF_UE_NGAP_ID, &amf_ue_ngap_id);
    ABTS_TRUE(tc, 0xffffffffff == amf_ue_ngap_id);
    ABTS_PTR_NOTNULL(tc, RAN_UE_NGAP_ID);
    ABTS_INT_EQUAL(tc, 1, *RAN_UE_NGAP_ID);
    ABTS_PTR_NOTNULL(tc, NAS_PDU);

    ogs_ngap_free(&message);

    ogs_pkbuf_free(ngapbuf);
}

abts_suite *test_ngap_message(abts_suite *suite)
{
    suite = ADD_SUITE(suite)

    ogs_log_install_domain(&__ogs_ngap_domain, "ngap", OGS_LOG_ERROR);

    abts_run_test(suite, ngap_message_test1, NULL);
    abts_run_test(suite, ngap_message_test2, NULL);
    abts_run_test(suite, ngap_message_test3, NULL);
    abts_run_test(suite, ngap_message_test4, NULL);
    abts_run_test(suite, ngap_message_test5_issues2934, NULL);

    return suite;
}
