/*
 * SPDX-License-Identifier: LicenseRef-CSSL-1.0
 */

/*!
 * \brief Unit tests for E1AP libraries
 */

#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>

#include "common/utils/utils.h"
#include "e1ap_bearer_context_management.h"
#include "e1ap_interface_management.h"
#include "e1ap_lib_includes.h"
#include "common/utils/assertions.h"

void exit_function(const char *file, const char *function, const int line, const char *s, const int assert)
{
  printf("detected error at %s:%d:%s: %s\n", file, line, function, s);
  abort();
}

static E1AP_E1AP_PDU_t *e1ap_encode_decode(const E1AP_E1AP_PDU_t *enc_pdu)
{
  // xer_fprint(stdout, &asn_DEF_E1AP_E1AP_PDU, enc_pdu);

  DevAssert(enc_pdu != NULL);
  char errbuf[2048];
  size_t errlen = sizeof(errbuf);
  int ret = asn_check_constraints(&asn_DEF_E1AP_E1AP_PDU, enc_pdu, errbuf, &errlen);
  AssertFatal(ret == 0, "asn_check_constraints() failed: %s\n", errbuf);

  uint8_t msgbuf[16384];
  asn_enc_rval_t enc = aper_encode_to_buffer(&asn_DEF_E1AP_E1AP_PDU, NULL, enc_pdu, msgbuf, sizeof(msgbuf));
  AssertFatal(enc.encoded > 0, "aper_encode_to_buffer() failed\n");

  E1AP_E1AP_PDU_t *dec_pdu = NULL;
  asn_codec_ctx_t st = {.max_stack_size = 100 * 1000};
  asn_dec_rval_t dec = aper_decode(&st, &asn_DEF_E1AP_E1AP_PDU, (void **)&dec_pdu, msgbuf, enc.encoded, 0, 0);
  AssertFatal(dec.code == RC_OK, "aper_decode() failed\n");

  // xer_fprint(stdout, &asn_DEF_E1AP_E1AP_PDU, dec_pdu);
  return dec_pdu;
}

static void e1ap_msg_free(E1AP_E1AP_PDU_t *pdu)
{
  ASN_STRUCT_FREE(asn_DEF_E1AP_E1AP_PDU, pdu);
}

static UP_TL_information_t create_up_tl_info(void)
{
  UP_TL_information_t tl_info;
  tl_info.tlAddress = htonl(0xC0A90001); // 192.169.0.1
  tl_info.teId = 0x2345;
  return tl_info;
}

/**
 * @brief Test E1AP Bearer Context Setup Request encoding/decoding
 */
static void test_bearer_context_setup_request(void)
{
  bearer_context_pdcp_config_t pdcp = {
    .discardTimer = E1AP_DiscardTimer_ms10,
    .pDCP_Reestablishment = true,
    .pDCP_SN_Size_DL = E1AP_PDCP_SN_Size_s_12,
    .pDCP_SN_Size_UL = E1AP_PDCP_SN_Size_s_12,
    .reorderingTimer = 10,
    .rLC_Mode = E1AP_RLC_Mode_rlc_am,
  };

  bearer_context_sdap_config_t sdap = {
    .defaultDRB = 1,
    .sDAP_Header_DL = true,
    .sDAP_Header_UL = true,
  };

  qos_flow_to_setup_t qos = {
    .qfi = 1,
    .qos_params.alloc_reten_priority.preemption_capability = E1AP_Pre_emptionCapability_shall_not_trigger_pre_emption,
    .qos_params.alloc_reten_priority.preemption_vulnerability = E1AP_Pre_emptionVulnerability_not_pre_emptable,
    .qos_params.alloc_reten_priority.priority_level = E1AP_PriorityLevel_highest,
    .qos_params.qos_characteristics.non_dynamic.fiveqi = 9,
  };

  security_indication_t security = {
    .confidentialityProtectionIndication = E1AP_ConfidentialityProtectionIndication_required,
    .integrityProtectionIndication = E1AP_IntegrityProtectionIndication_required,
    .maxIPrate = E1AP_MaxIPrate_max_UErate,
  };

  // Step 1: Initialize the E1AP Bearer Context Setup Request
  e1ap_bearer_setup_req_t orig = {
      .gNB_cu_cp_ue_id = 1234,
      .secInfo.cipheringAlgorithm = 0x01,
      .secInfo.integrityProtectionAlgorithm = 0x01,
      .ueDlAggMaxBitRate = 1000000000,
      .bearerContextStatus = 0,
      .servingPLMNid.mcc = 001,
      .servingPLMNid.mnc = 01,
      .servingPLMNid.mnc_digit_length = 0x02,
      .numPDUSessions = 1,
      .pduSession[0].sessionId = 1,
      .pduSession[0].sessionType = E1AP_PDU_Session_Type_ipv4,
      .pduSession[0].nssai.sd = 0x01,
      .pduSession[0].nssai.sst = 0x01,
      .pduSession[0].securityIndication = security,
      .pduSession[0].numDRB2Setup = 1,
      .pduSession[0].UP_TL_information = create_up_tl_info(),
      .pduSession[0].DRBnGRanList[0].id = 1,
      .pduSession[0].DRBnGRanList[0].sdap_config = sdap,
      .pduSession[0].DRBnGRanList[0].pdcp_config = pdcp,
      .pduSession[0].DRBnGRanList[0].id = 1,
      .pduSession[0].DRBnGRanList[0].numCellGroups = 1,
      .pduSession[0].DRBnGRanList[0].cellGroupList[0] = MCG,
      .pduSession[0].DRBnGRanList[0].numQosFlow2Setup = 1,
      .pduSession[0].DRBnGRanList[0].qosFlows[0] = qos,
  };
  memset(orig.secInfo.encryptionKey, 0xAB, sizeof(orig.secInfo.encryptionKey));
  memset(orig.secInfo.integrityProtectionKey, 0xCD, sizeof(orig.secInfo.integrityProtectionKey));
  // E1AP encode the original message
  E1AP_E1AP_PDU_t *enc = encode_E1_bearer_context_setup_request(&orig);
  // E1AP decode the encoded message
  E1AP_E1AP_PDU_t *dec = e1ap_encode_decode(enc);
  // Free the E1AP encoded message
  e1ap_msg_free(enc);
  // E1 message decode
  e1ap_bearer_setup_req_t decoded = {0};
  bool ret = decode_E1_bearer_context_setup_request(dec, &decoded);
  AssertFatal(ret, "decode_E1_bearer_context_setup_request(): could not decode message\n");
  // Free the E1AP decoded message
  e1ap_msg_free(dec);
  // Equality check original/decoded
  ret = eq_bearer_context_setup_request(&orig, &decoded);
  AssertFatal(ret, "eq_bearer_context_setup_request(): decoded message doesn't match\n");
  // Free the memory for the decoded message
  free_e1ap_context_setup_request(&decoded);
  // Deep copy and equality check of the original message
  e1ap_bearer_setup_req_t cp = cp_bearer_context_setup_request(&orig);
  ret = eq_bearer_context_setup_request(&orig, &cp);
  AssertFatal(ret, "eq_bearer_context_setup_request(): copied message doesn't match\n");
  // Free the copied message and original
  free_e1ap_context_setup_request(&cp);
  free_e1ap_context_setup_request(&orig);
}

/**
 * @brief Test E1AP Bearer Context Setup Response encoding/decoding
 */
static void test_bearer_context_setup_response(void)
{
  // Step 1: Initialize the E1AP Bearer Context Setup Response
  e1ap_bearer_setup_resp_t orig = {
      .gNB_cu_cp_ue_id = 1234,
      .gNB_cu_up_ue_id = 5678,
      .numPDUSessions = 1,
      .pduSession[0].id = 1,
      .pduSession[0].tl_info = create_up_tl_info(),
      .pduSession[0].numDRBSetup = 1,
      .pduSession[0].numDRBFailed = 0,
      .pduSession[0].DRBnGRanList[0].id = 1,
      .pduSession[0].DRBnGRanList[0].numUpParam = 1,
      .pduSession[0].DRBnGRanList[0].numQosFlowSetup = 1,
      .pduSession[0].DRBnGRanList[0].qosFlows[0].qfi = 1,
      .pduSession[0].DRBnGRanList[0].UpParamList[0].cell_group_id = MCG,
      .pduSession[0].DRBnGRanList[0].UpParamList[0].tl_info = create_up_tl_info(),
  };
  // E1AP encode the original message
  E1AP_E1AP_PDU_t *enc = encode_E1_bearer_context_setup_response(&orig);

  // E1AP decode the encoded message
  E1AP_E1AP_PDU_t *dec = e1ap_encode_decode(enc);

  // Free the E1AP encoded message
  e1ap_msg_free(enc);

  // E1 message decode
  e1ap_bearer_setup_resp_t decoded = {0};
  bool ret = decode_E1_bearer_context_setup_response(dec, &decoded);
  AssertFatal(ret, "decode_E1_bearer_context_setup_response(): could not decode message\n");

  // Free the E1AP decoded message
  e1ap_msg_free(dec);

  // Equality check original/decoded
  ret = eq_bearer_context_setup_response(&orig, &decoded);
  AssertFatal(ret, "eq_bearer_context_setup_response(): decoded message doesn't match\n");

  // Free the memory for the decoded message
  free_e1ap_context_setup_response(&decoded);

  // Deep copy and equality check of the original message
  e1ap_bearer_setup_resp_t cp = cp_bearer_context_setup_response(&orig);
  ret = eq_bearer_context_setup_response(&orig, &cp);
  AssertFatal(ret, "eq_bearer_context_setup_response(): copied message doesn't match\n");

  // Free the copied message and original
  free_e1ap_context_setup_response(&cp);
  free_e1ap_context_setup_response(&orig);
}

/** @brief Test E1AP Bearer Context Setup Failure encoding/decoding */
static void test_bearer_context_setup_failure(void)
{
  e1ap_bearer_context_setup_failure_t orig = {
      .gNB_cu_cp_ue_id = 0x1234,
      .gNB_cu_up_ue_id = malloc_or_fail(sizeof(*orig.gNB_cu_up_ue_id)),
      .cause.type = E1AP_Cause_PR_radioNetwork,
      .cause.value = 11,
  };
  *orig.gNB_cu_up_ue_id = 0x5678;

  // Encode the original message
  E1AP_E1AP_PDU_t *enc = encode_E1_bearer_context_setup_failure(&orig);

  // E1AP decode the encoded message
  E1AP_E1AP_PDU_t *dec = e1ap_encode_decode(enc);

  // Free the E1AP encoded message
  e1ap_msg_free(enc);

  // Decode the encoded message
  e1ap_bearer_context_setup_failure_t decoded = {0};
  bool ret = decode_E1_bearer_context_setup_failure(&decoded, dec);
  AssertFatal(ret, "decode_E1_bearer_context_setup_failure failed");

  // Free the E1AP decoded message
  e1ap_msg_free(dec);

  // Equality check original/decoded
  AssertFatal(eq_bearer_context_setup_failure(&orig, &decoded), "eq_bearer_context_mod_failure: decoded message does not match original");

  // Free the memory for the decoded message
  free_e1_bearer_context_setup_failure(&decoded);

  // Deep copy and equality check of the original message
  e1ap_bearer_context_setup_failure_t cp = cp_bearer_context_setup_failure(&orig);
  AssertFatal(eq_bearer_context_setup_failure(&orig, &cp), "eq_bearer_context_setup_failure(): copied message doesn't match\n");

  // Cleanup
  free_e1_bearer_context_setup_failure(&orig);
  free_e1_bearer_context_setup_failure(&cp);
}

/** @brief Test E1AP Bearer Context Release Command encoding/decoding */
static void test_bearer_context_release_command(void)
{
  e1ap_bearer_release_cmd_t orig = {
      .gNB_cu_cp_ue_id = 0x1234,
      .gNB_cu_up_ue_id = 0x5678,
      .cause.type = E1AP_CAUSE_RADIO_NETWORK,
      .cause.value = E1AP_RADIO_CAUSE_MULTIPLE_QOS_FLOW_ID_INSTANCES,
  };

  // Encode the original message
  E1AP_E1AP_PDU_t *enc = encode_e1_bearer_context_release_command(&orig);

  // E1AP decode the encoded message
  E1AP_E1AP_PDU_t *dec = e1ap_encode_decode(enc);

  // Free the E1AP encoded message
  e1ap_msg_free(enc);

  // Decode the encoded message
  e1ap_bearer_release_cmd_t decoded = {0};
  AssertFatal(decode_e1_bearer_context_release_command(&decoded, dec), "decode_e1_bearer_context_release_command failed");

  // Free the E1AP decoded message
  e1ap_msg_free(dec);

  // Equality check original/decoded
  AssertFatal(eq_bearer_context_release_command(&orig, &decoded), "eq_bearer_context_release_command: decoded message does not match original");

  // Free the memory for the decoded message
  free_e1_bearer_context_release_command(&decoded);

  // Deep copy and equality check of the original message
  e1ap_bearer_release_cmd_t cp = cp_bearer_context_release_command(&orig);
  AssertFatal(eq_bearer_context_release_command(&orig, &cp), "eq_bearer_context_release_command(): copied message doesn't match\n");

  // Cleanup
  free_e1_bearer_context_release_command(&orig);
  free_e1_bearer_context_release_command(&cp);
}

/** @brief Test E1AP Bearer Context Release Complete encoding/decoding */
static void test_bearer_context_release_complete(void)
{
  e1ap_bearer_release_cplt_t orig = {
      .gNB_cu_cp_ue_id = 0x1234,
      .gNB_cu_up_ue_id = 0x5678,
  };

  // Encode the original message
  E1AP_E1AP_PDU_t *enc = encode_e1_bearer_context_release_complete(&orig);

  // E1AP decode the encoded message
  E1AP_E1AP_PDU_t *dec = e1ap_encode_decode(enc);

  // Free the E1AP encoded message
  e1ap_msg_free(enc);

  // Decode the encoded message
  e1ap_bearer_release_cplt_t decoded = {0};
  AssertFatal(decode_e1_bearer_context_release_complete(&decoded, dec), "decode_e1_bearer_context_release_complete failed");

  // Free the E1AP decoded message
  e1ap_msg_free(dec);

  // Equality check original/decoded
  AssertFatal(eq_bearer_context_release_complete(&orig, &decoded), "eq_bearer_context_release_complete: decoded message does not match original");

  // Free the memory for the decoded message
  free_e1_bearer_context_release_complete(&decoded);

  // Deep copy and equality check of the original message
  e1ap_bearer_release_cplt_t cp = cp_bearer_context_release_complete(&orig);
  AssertFatal(eq_bearer_context_release_complete(&orig, &cp), "eq_bearer_context_release_complete(): copied message doesn't match\n");

  // Cleanup
  free_e1_bearer_context_release_complete(&orig);
  free_e1_bearer_context_release_complete(&cp);
}

/**
 * @brief Test CU-UP Setup Request encoding/decoding
 */
static void test_e1_cuup_setup_request(void)
{
  e1ap_setup_req_t orig = {.gNB_cu_up_id = 1234,
                           .gNB_cu_up_name = strdup("OAI CU-UP"),
                           .transac_id = 42,
                           .supported_plmns = 1,
                           .cn_support = cn_support_5GC,
                           .plmn[0] = {.id = {.mcc = 001, .mnc = 01, .mnc_digit_length = 2},
                                       .supported_slices = 1}};
  orig.plmn[0].slice = malloc_or_fail(sizeof(*orig.plmn[0].slice));
  orig.plmn[0].slice->sst = 0x01;
  orig.plmn[0].slice->sd = 0x01;

  E1AP_E1AP_PDU_t *encoded = encode_e1ap_cuup_setup_request(&orig);
  E1AP_E1AP_PDU_t *decoded_msg = e1ap_encode_decode(encoded);
  e1ap_msg_free(encoded);

  e1ap_setup_req_t decoded = {0};
  bool ret = decode_e1ap_cuup_setup_request(decoded_msg, &decoded);
  AssertFatal(ret, "Failed to decode setup request");
  e1ap_msg_free(decoded_msg);

  ret = eq_e1ap_cuup_setup_request(&orig, &decoded);
  AssertFatal(ret, "Decoded setup request doesn't match original");
  free_e1ap_cuup_setup_request(&decoded);

  e1ap_setup_req_t cp = cp_e1ap_cuup_setup_request(&orig);
  ret = eq_e1ap_cuup_setup_request(&orig, &cp);
  AssertFatal(ret, "eq_e1ap_cuup_setup_request(): copied message doesn't match\n");

  free_e1ap_cuup_setup_request(&orig);
  free_e1ap_cuup_setup_request(&cp);
}

/**
 * @brief Test CU-UP Setup Response encoding/decoding
 */
static void test_e1_cuup_setup_response(void)
{
  e1ap_setup_resp_t orig = {.transac_id = 42,
                            .gNB_cu_cp_name = strdup("OAI CU-CP"),
                            .tnla_info = malloc(sizeof(tnl_address_info_t))};
  orig.tnla_info->num_addresses_to_add = 1;
  orig.tnla_info->num_addresses_to_remove = 1;
  orig.tnla_info->addresses_to_add[0].num_gtp_tl_addresses = 1;
  orig.tnla_info->addresses_to_add[0].ipsec_tl_address = 0xC0A80001;
  orig.tnla_info->addresses_to_add[0].gtp_tl_addresses[0] = 0xC0A80002;
  orig.tnla_info->num_addresses_to_remove = 1;
  orig.tnla_info->addresses_to_remove[0].num_gtp_tl_addresses = 1;
  orig.tnla_info->addresses_to_remove[0].ipsec_tl_address = 0xC0A80003;
  orig.tnla_info->addresses_to_remove[0].gtp_tl_addresses[0] = 0xC0A80004;

  E1AP_E1AP_PDU_t *encoded = encode_e1ap_cuup_setup_response(&orig);
  E1AP_E1AP_PDU_t *decoded_msg = e1ap_encode_decode(encoded);
  e1ap_msg_free(encoded);

  e1ap_setup_resp_t decoded = {0};
  bool ret = decode_e1ap_cuup_setup_response(decoded_msg, &decoded);
  AssertFatal(ret, "Failed to decode setup response");
  e1ap_msg_free(decoded_msg);

  ret = eq_e1ap_cuup_setup_response(&orig, &decoded);
  AssertFatal(ret, "Decoded setup response doesn't match original");
  free_e1ap_cuup_setup_response(&decoded);

  e1ap_setup_resp_t cp = cp_e1ap_cuup_setup_response(&orig);
  ret = eq_e1ap_cuup_setup_response(&orig, &cp);
  AssertFatal(ret, "eq_e1ap_cuup_setup_response(): copied message doesn't match\n");

  free_e1ap_cuup_setup_response(&orig);
  free_e1ap_cuup_setup_response(&cp);
}

// Test for E1AP CU-UP Setup Failure
static void test_e1_cuup_setup_failure(void)
{
  e1ap_setup_fail_t orig = {.transac_id = 42,
                            .cause.type = E1AP_CAUSE_RADIO_NETWORK,
                            .cause.value = E1AP_RADIO_CAUSE_NORMAL_RELEASE,
                            .time_to_wait = malloc_or_fail(sizeof(long)),
                            .crit_diag = malloc_or_fail(sizeof(criticality_diagnostics_t))};
  *orig.time_to_wait = 5;
  orig.crit_diag->procedure_code = malloc_or_fail(sizeof(*orig.crit_diag->procedure_code));
  *orig.crit_diag->procedure_code = 99;
  orig.crit_diag->triggering_msg = malloc_or_fail(sizeof(*orig.crit_diag->triggering_msg));
  *orig.crit_diag->triggering_msg = TRIGGERING_MSG_SUCCESSFUL_OUTCOME;
  orig.crit_diag->procedure_criticality = malloc_or_fail(sizeof(*orig.crit_diag->procedure_criticality));
  *orig.crit_diag->procedure_criticality = CRITICALITY_IGNORE;
  orig.crit_diag->num_errors = 1;
  orig.crit_diag->errors[0].ie_id = 66;
  orig.crit_diag->errors[0].error_type = ERROR_TYPE_MISSING;
  orig.crit_diag->errors[0].criticality = CRITICALITY_IGNORE;

  E1AP_E1AP_PDU_t *encoded = encode_e1ap_cuup_setup_failure(&orig);
  E1AP_E1AP_PDU_t *decoded_msg = e1ap_encode_decode(encoded);
  e1ap_msg_free(encoded);

  e1ap_setup_fail_t decoded = {0};
  bool ret = decode_e1ap_cuup_setup_failure(decoded_msg, &decoded);
  AssertFatal(ret, "Failed to decode setup failure");
  e1ap_msg_free(decoded_msg);

  ret = eq_e1ap_cuup_setup_failure(&orig, &decoded);
  AssertFatal(ret, "Decoded setup failure doesn't match original");
  free_e1ap_cuup_setup_failure(&decoded);

  e1ap_setup_fail_t cp = cp_e1ap_cuup_setup_failure(&orig);
  ret = eq_e1ap_cuup_setup_failure(&orig, &cp);
  AssertFatal(ret, "eq_e1ap_cuup_setup_failure(): copied message doesn't match\n");

  free_e1ap_cuup_setup_failure(&cp);
  free_e1ap_cuup_setup_failure(&orig);
}

/**
 * @brief Test E1AP Bearer Context Modification Request encoding/decoding
 */
static void test_bearer_context_modification_request(void)
{
  const bearer_context_sdap_config_t dummy_sdap_config = {
      .defaultDRB = 0,
      .sDAP_Header_DL = false,
      .sDAP_Header_UL = false,
  };

  const bearer_context_pdcp_config_t dummy_pdcp_config = {
      .discardTimer = E1AP_DiscardTimer_ms100,
      .pDCP_Reestablishment = true,
      .pDCP_SN_Size_DL = E1AP_PDCP_SN_Size_s_12,
      .pDCP_SN_Size_UL = E1AP_PDCP_SN_Size_s_12,
      .reorderingTimer = 5,
      .rLC_Mode = E1AP_RLC_Mode_rlc_um_bidirectional,
  };

  const qos_flow_to_setup_t dummy_qos_flows = {
      .qfi = 9,
      .qos_params.alloc_reten_priority.preemption_capability = E1AP_Pre_emptionCapability_may_trigger_pre_emption,
      .qos_params.alloc_reten_priority.preemption_vulnerability = E1AP_Pre_emptionVulnerability_pre_emptable,
      .qos_params.alloc_reten_priority.priority_level = E1AP_PriorityLevel_no_priority,
      .qos_params.qos_characteristics.non_dynamic.fiveqi = 9,
  };

  const e1_pdcp_status_info_t dummy_pdcp_status = {
      .dl_count.hfn = 12,
      .dl_count.sn = 34,
      .ul_count.hfn = 56,
      .ul_count.sn = 78,
  };

  DRB_nGRAN_to_mod_t drb_to_mod = {
    .numDlUpParam = 1,
    .DlUpParamList[0].cell_group_id = MCG,
    .DlUpParamList[0].tl_info = create_up_tl_info(),
    .id = 1,
    .pdcp_config = malloc_or_fail(sizeof(*drb_to_mod.pdcp_config)),
    .pdcp_sn_status_requested = true,
    .pdcp_status = malloc_or_fail(sizeof(*drb_to_mod.pdcp_status)),
    .numQosFlowsMod = 1,
    .qosFlows[0] = dummy_qos_flows,
  };
  *drb_to_mod.pdcp_config = dummy_pdcp_config;
  *drb_to_mod.pdcp_status = dummy_pdcp_status;

  pdu_session_to_mod_t pdusession_mod_item = {
      .sessionId = 1,
      .numDRB2Modify = 1,
      .DRBnGRanModList[0] = drb_to_mod,
      .n_drb_to_remove = 1,
      .drbs_to_remove[0].id = 2,
  };

  DRB_nGRAN_to_setup_t drb_to_setup = {
      .id = 1,
      .cellGroupList[0] = MCG,
      .numCellGroups = 1,
      .pdcp_config = dummy_pdcp_config,
      .sdap_config = dummy_sdap_config,
      .numQosFlow2Setup = 1,
      .qosFlows[0] = dummy_qos_flows,
      .drb_inactivity_timer = malloc_or_fail(sizeof(*drb_to_setup.drb_inactivity_timer)),
  };
  *drb_to_setup.drb_inactivity_timer = 500;

  pdu_session_to_setup_t pdusession_setup_item = {
      .numDRB2Setup = 1,
      .nssai.sd = 0x01,
      .nssai.sst = 0x01,
      .UP_TL_information.teId = 0x12345,
      .UP_TL_information.tlAddress = 167772161,
      .DRBnGRanList[0] = drb_to_setup,
  };

  // PDU Session to Remove
  pdu_session_to_remove_t pdu_session_to_remove = {
      .sessionId = 2,
      // Note: cause is optional extension IE, currently encoding/decoding only handles sessionId
      .cause.type = 0,
      .cause.value = 0,
  };

  // Initialize the Bearer Context Modification Request
  e1ap_bearer_mod_req_t orig = {
      .gNB_cu_cp_ue_id = 0x1234,
      .gNB_cu_up_ue_id = 0x5678,
      .bearerContextStatus = malloc_or_fail(sizeof(*orig.bearerContextStatus)),
      .inactivityTimer = malloc_or_fail(sizeof(*orig.inactivityTimer)),
      .numPDUSessions = 1,
      .pduSession[0] = pdusession_setup_item,
      .numPDUSessionsMod = 1,
      .pduSessionMod[0] = pdusession_mod_item,
      .numPDUSessionsRem = 1,
      .pduSessionRem[0] = pdu_session_to_remove,
  };
  *orig.bearerContextStatus = BEARER_SUSPEND;
  *orig.inactivityTimer = 1000;

  // Encode the original message
  E1AP_E1AP_PDU_t *enc = encode_E1_bearer_context_mod_request(&orig);

  // Decode the encoded message
  E1AP_E1AP_PDU_t *dec = e1ap_encode_decode(enc);

  // Free the encoded message
  e1ap_msg_free(enc);

  // Decode the message into a new struct
  e1ap_bearer_mod_req_t decoded = {0};
  AssertFatal(decode_E1_bearer_context_mod_request(dec, &decoded), "decode_E1_bearer_context_mod_request(): could not decode message\n");

  // Free the decoded E1AP message
  e1ap_msg_free(dec);

  // Compare the original and decoded structs
  AssertFatal(eq_bearer_context_mod_request(&orig, &decoded), "eq_bearer_context_mod_request(): decoded message doesn't match\n");

  // Free the memory for the decoded message
  free_e1ap_context_mod_request(&decoded);

  // Deep copy the original message
  e1ap_bearer_mod_req_t cp = cp_bearer_context_mod_request(&orig);

  // Verify the deep copy matches the original
  AssertFatal(eq_bearer_context_mod_request(&orig, &cp), "eq_bearer_context_mod_request(): copied message doesn't match\n");

  // Free the copied and original message
  free_e1ap_context_mod_request(&cp);
  free_e1ap_context_mod_request(&orig);
}

const e1ap_cause_t dummy_cause = {
  .value = E1AP_RADIO_CAUSE_UNSPECIFIED,
  .type = E1AP_CAUSE_RADIO_NETWORK,
};

const DRB_nGRAN_failed_t dummy_drb_failed = {
    .id = 2,
    .cause.value = E1AP_RADIO_CAUSE_UNKNOWN_DRB_ID,
    .cause.type = E1AP_CAUSE_RADIO_NETWORK,
};

/**
 * @brief Test E1AP Bearer Context Modification Response encoding/decoding
 */
static void test_bearer_context_modification_response(void)
{
  const e1_pdcp_status_info_t dummy_pdcp_status = {
      .dl_count.hfn = 12,
      .dl_count.sn = 2,
      .ul_count.hfn = 12,
      .ul_count.sn = 3,
  };

  // DRB Modified List
  DRB_nGRAN_modified_t drb_mod = {
      .id = 1,
      .numQosFlowSetup = 1,
      .qosFlows[0].qfi = 1,
      .pdcp_status = malloc_or_fail(sizeof(*drb_mod.pdcp_status)),
  };
  *drb_mod.pdcp_status = dummy_pdcp_status;

  // DRB Setup List
  DRB_nGRAN_setup_t setup = {
      .id = 1,
      .numUpParam = 1,
      .UpParamList[0].cell_group_id = MCG,
      .UpParamList[0].tl_info = create_up_tl_info(),
      .numQosFlowSetup = 1,
      .qosFlows[0].qfi = 1,
      .numQosFlowFailed = 1,
      .qosFlowsFailed[0].qfi = 9,
      .qosFlowsFailed[0].cause = dummy_cause,
  };

  // PDU Session Modified
  pdu_session_modif_t pdu_mod = {
      .id = 1,
      .integrityProtectionIndication = malloc_or_fail(sizeof(*pdu_mod.integrityProtectionIndication)),
      .confidentialityProtectionIndication = malloc_or_fail(sizeof(*pdu_mod.confidentialityProtectionIndication)),
      .ng_DL_UP_TL_info = malloc_or_fail(sizeof(*pdu_mod.ng_DL_UP_TL_info)),
      .numDRBModified = 1,
      .DRBnGRanModList[0] = drb_mod,
      .numDRBFailedToMod = 1,
      .DRBnGRanFailedModList[0] = dummy_drb_failed,
      .numDRBSetup = 1,
      .DRBnGRanSetupList[0] = setup,
      .numDRBFailed = 1,
      .DRBnGRanFailedList[0] = dummy_drb_failed,
  };

  *pdu_mod.integrityProtectionIndication = SECURITY_PREFERRED;
  *pdu_mod.confidentialityProtectionIndication = SECURITY_PREFERRED;
  *pdu_mod.ng_DL_UP_TL_info = create_up_tl_info();

  e1ap_bearer_modif_resp_t orig = {
      .gNB_cu_cp_ue_id = 0x1234,
      .gNB_cu_up_ue_id = 0x5678,
      .numPDUSessionsMod = 1,
      .pduSessionMod[0] = pdu_mod,
  };

  // Encode the original message
  E1AP_E1AP_PDU_t *enc = encode_E1_bearer_context_mod_response(&orig);

  // Decode the encoded message
  E1AP_E1AP_PDU_t *dec = e1ap_encode_decode(enc);

  // Free the encoded message
  e1ap_msg_free(enc);

  // Decode the message into a new struct
  e1ap_bearer_modif_resp_t decoded = {0};
  bool ret = decode_E1_bearer_context_mod_response(&decoded, dec);
  AssertFatal(ret, "decode_E1_bearer_context_mod_response(): could not decode message\n");

  // Free the decoded E1AP message
  e1ap_msg_free(dec);

  // Compare the original and decoded structs
  ret = eq_bearer_context_mod_response(&orig, &decoded);
  AssertFatal(ret, "eq_bearer_context_mod_response(): decoded message doesn't match\n");

  // Free the memory for the decoded message
  free_e1ap_context_mod_response(&decoded);

  // Deep copy the original message
  e1ap_bearer_modif_resp_t cp = cp_bearer_context_mod_response(&orig);

  // Verify the deep copy matches the original
  ret = eq_bearer_context_mod_response(&orig, &cp);
  AssertFatal(ret, "eq_bearer_context_mod_response(): copied message doesn't match\n");

  // Free the copied and original message
  free_e1ap_context_mod_response(&cp);
  free_e1ap_context_mod_response(&orig);
}

/** @brief Test E1AP Bearer Context Modification Response encoding/decoding
 *         with failed DRBs (setup and modification) */
static void test_bearer_context_modification_response_fail(void)
{
  const e1ap_cause_t cause = {
      .value = E1AP_PROTOCOL_CAUSE_SEMANTIC_ERROR,
      .type = E1AP_CAUSE_PROTOCOL,
  };

  DRB_nGRAN_failed_t failed_setup_drb = {
      .id = 5,
      .cause = cause,
  };

  DRB_nGRAN_failed_t failed_mod_drb = {
      .id = 6,
      .cause = cause,
  };

  pdu_session_modif_t pdu_mod = {
      .id = 2,
      .numDRBModified = 0,
      .numDRBFailedToMod = 1,
      .DRBnGRanFailedModList[0] = failed_mod_drb,
      .numDRBSetup = 0,
      .numDRBFailed = 1,
      .DRBnGRanFailedList[0] = failed_setup_drb,
      .integrityProtectionIndication = NULL,
      .confidentialityProtectionIndication = NULL,
      .ng_DL_UP_TL_info = NULL,
  };

  e1ap_bearer_modif_resp_t orig = {
      .gNB_cu_cp_ue_id = 0xABCD,
      .gNB_cu_up_ue_id = 0xDCBA,
      .numPDUSessionsMod = 1,
      .pduSessionMod[0] = pdu_mod,
  };

  // Encode the original message
  E1AP_E1AP_PDU_t *enc = encode_E1_bearer_context_mod_response(&orig);

  // Decode the encoded message
  E1AP_E1AP_PDU_t *dec = e1ap_encode_decode(enc);

  // Free the encoded message
  e1ap_msg_free(enc);

  // Decode into a new struct
  e1ap_bearer_modif_resp_t decoded = {0};
  bool ret = decode_E1_bearer_context_mod_response(&decoded, dec);
  AssertFatal(ret, "decode_E1_bearer_context_mod_response(): could not decode failed DRB setup+mod case\n");

  // Free the decoded E1AP message
  e1ap_msg_free(dec);

  // Compare the original and decoded structs
  ret = eq_bearer_context_mod_response(&orig, &decoded);
  AssertFatal(ret, "eq_bearer_context_mod_response(): failed DRB setup+mod case mismatch\n");

  // Free all memory
  free_e1ap_context_mod_response(&decoded);
  free_e1ap_context_mod_response(&orig);
}

/** @brief Test E1AP Bearer Context Modification Failure encoding/decoding */
static void test_bearer_context_modification_failure(void)
{
  // Create the original failure struct
  e1ap_bearer_context_mod_failure_t orig = {
      .gNB_cu_cp_ue_id = 0x1111,
      .gNB_cu_up_ue_id = 0x2222,
      .cause.type = E1AP_CAUSE_TRANSPORT,
      .cause.value = E1AP_TRANSPORT_CAUSE_RESOURCE_UNAVAILABLE,
  };

  // Encode the original message
  E1AP_E1AP_PDU_t *enc = encode_E1_bearer_context_mod_failure(&orig);

  // Decode the encoded message
  E1AP_E1AP_PDU_t *dec = e1ap_encode_decode(enc);

  // Free the encoded message
  e1ap_msg_free(enc);

  // Decode into a new struct
  e1ap_bearer_context_mod_failure_t decoded = {0};
  AssertFatal(decode_E1_bearer_context_mod_failure(&decoded, dec),
              "decode_E1_bearer_context_mod_failure(): could not decode message\n");

  // Free the decoded PDU
  e1ap_msg_free(dec);

  // Compare original and decoded messages
  AssertFatal(eq_E1_bearer_context_mod_failure(&orig, &decoded),
              "eq_E1_bearer_context_mod_failure(): decoded message doesn't match\n");

  // Deep copy the original message
  e1ap_bearer_context_mod_failure_t cp = cp_E1_bearer_context_mod_failure(&orig);

  // Compare original and copied messages
  AssertFatal(eq_E1_bearer_context_mod_failure(&orig, &cp), "eq_E1_bearer_context_mod_failure(): copied message doesn't match\n");

  // Free the decoded and copied messages
  free_E1_bearer_context_mod_failure(&decoded);
  free_E1_bearer_context_mod_failure(&cp);
}

int main()
{
  // E1 Bearer Context Setup
  test_bearer_context_setup_request();
  test_bearer_context_setup_response();
  test_bearer_context_setup_failure();
  // E1 Interface Management
  test_e1_cuup_setup_request();
  test_e1_cuup_setup_response();
  test_e1_cuup_setup_failure();
  // E1 Bearer Context Modification
  test_bearer_context_modification_request();
  test_bearer_context_modification_response();
  test_bearer_context_modification_response_fail();
  test_bearer_context_modification_failure();
  // Bearer Context Release
  test_bearer_context_release_command();
  test_bearer_context_release_complete();
  return 0;
}
