/*
 * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The OpenAirInterface Software Alliance licenses this file to You under
 * the OAI Public License, Version 1.1  (the "License"); you may not use this file
 * except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.openairinterface.org/?page_id=698
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *-------------------------------------------------------------------------------
 * For more information about the OpenAirInterface (OAI) Software Alliance:
 *      contact@openairinterface.org
 */

/*! \file flexran_agent_common.c
 * \brief common primitives for all agents
 * \author Xenofon Foukas, Mohamed Kassem and Navid Nikaein
 * \date 2017
 * \version 0.1
 */

#include <stdio.h>
#include <time.h>
#include <sys/stat.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <errno.h>

#include "flexran_agent_common.h"
#include "flexran_agent_common_internal.h"
#include "flexran_agent_extern.h"
#include "flexran_agent_net_comm.h"
#include "flexran_agent_ran_api.h"
#include "flexran_agent_phy.h"
#include "flexran_agent_mac.h"
#include "flexran_agent_rrc.h"
#include "flexran_agent_s1ap.h"
#include "flexran_agent_app.h"
//#include "PHY/extern.h"
#include "common/utils/LOG/log.h"
#include "flexran_agent_mac_internal.h"
#include "flexran_agent_rrc_internal.h"

//#include "SCHED/defs.h"
#include "RRC/LTE/rrc_extern.h"
#include "RRC/L2_INTERFACE/openair_rrc_L2_interface.h"
#include "rrc_eNB_UE_context.h"

/*
 * message primitives
 */

int flexran_agent_serialize_message(Protocol__FlexranMessage *msg, void **buf, int *size) {
  *size = protocol__flexran_message__get_packed_size(msg);

  if (buf == NULL)
    goto error;

  *buf = malloc(*size);

  if (*buf == NULL)
    goto error;

  protocol__flexran_message__pack(msg, *buf);
  return 0;
error:
  LOG_E(FLEXRAN_AGENT, "an error occured\n"); // change the com
  return -1;
}



/* We assume that the buffer size is equal to the message size.
   Should be chekced durint Tx/Rx */
int flexran_agent_deserialize_message(void *data, int size, Protocol__FlexranMessage **msg) {
  *msg = protocol__flexran_message__unpack(NULL, size, data);

  if (*msg == NULL)
    goto error;

  return 0;
error:
  //LOG_E(MAC, "%s: an error occured\n", __FUNCTION__);
  return -1;
}



int flexran_create_header(xid_t xid, Protocol__FlexType type,  Protocol__FlexHeader **header) {
  *header = malloc(sizeof(Protocol__FlexHeader));

  if(*header == NULL)
    goto error;

  protocol__flex_header__init(*header);
  (*header)->version = FLEXRAN_VERSION;
  (*header)->has_version = 1;
  // check if the type is set
  (*header)->type = type;
  (*header)->has_type = 1;
  (*header)->xid = xid;
  (*header)->has_xid = 1;
  return 0;
error:
  LOG_E(FLEXRAN_AGENT, "%s: an error occured\n", __FUNCTION__);
  return -1;
}


int flexran_agent_hello(mid_t mod_id, const void *params, Protocol__FlexranMessage **msg) {
  Protocol__FlexHeader *header = NULL;
  /*TODO: Need to set random xid or xid from received hello message*/
  xid_t xid = 1;
  Protocol__FlexHello *hello_msg;
  hello_msg = malloc(sizeof(Protocol__FlexHello));

  if(hello_msg == NULL)
    goto error;

  protocol__flex_hello__init(hello_msg);

  if (flexran_create_header(xid, PROTOCOL__FLEX_TYPE__FLPT_HELLO, &header) != 0)
    goto error;

  hello_msg->header = header;
  hello_msg->bs_id  = flexran_get_bs_id(mod_id);
  hello_msg->has_bs_id = 1;
  hello_msg->n_capabilities = flexran_get_capabilities(mod_id, &hello_msg->capabilities);
  hello_msg->n_splits = flexran_get_splits(mod_id, &hello_msg->splits);
  *msg = malloc(sizeof(Protocol__FlexranMessage));

  if(*msg == NULL)
    goto error;

  protocol__flexran_message__init(*msg);
  (*msg)->msg_case = PROTOCOL__FLEXRAN_MESSAGE__MSG_HELLO_MSG;
  (*msg)->msg_dir = PROTOCOL__FLEXRAN_DIRECTION__SUCCESSFUL_OUTCOME;
  (*msg)->has_msg_dir = 1;
  (*msg)->hello_msg = hello_msg;
  return 0;
error:

  if(header != NULL)
    free(header);

  if(hello_msg != NULL)
    free(hello_msg);

  if(*msg != NULL)
    free(*msg);

  LOG_E(FLEXRAN_AGENT, "%s: an error occured\n", __FUNCTION__);
  return -1;
}


int flexran_agent_destroy_hello(Protocol__FlexranMessage *msg) {
  if(msg->msg_case != PROTOCOL__FLEXRAN_MESSAGE__MSG_HELLO_MSG)
    goto error;

  free(msg->hello_msg->header);
  free(msg->hello_msg->capabilities);
  free(msg->hello_msg->splits);
  free(msg->hello_msg);
  free(msg);
  return 0;
error:
  LOG_E(FLEXRAN_AGENT, "%s: an error occured\n", __FUNCTION__);
  return -1;
}


int flexran_agent_echo_request(mid_t mod_id, const void *params, Protocol__FlexranMessage **msg) {
  Protocol__FlexHeader *header = NULL;
  /*TODO: Need to set a random xid*/
  xid_t xid = 1;
  Protocol__FlexEchoRequest *echo_request_msg = NULL;
  echo_request_msg = malloc(sizeof(Protocol__FlexEchoRequest));

  if(echo_request_msg == NULL)
    goto error;

  protocol__flex_echo_request__init(echo_request_msg);

  if (flexran_create_header(xid, PROTOCOL__FLEX_TYPE__FLPT_ECHO_REQUEST, &header) != 0)
    goto error;

  echo_request_msg->header = header;
  *msg = malloc(sizeof(Protocol__FlexranMessage));

  if(*msg == NULL)
    goto error;

  protocol__flexran_message__init(*msg);
  (*msg)->msg_case = PROTOCOL__FLEXRAN_MESSAGE__MSG_ECHO_REQUEST_MSG;
  (*msg)->msg_dir = PROTOCOL__FLEXRAN_DIRECTION__INITIATING_MESSAGE;
  (*msg)->echo_request_msg = echo_request_msg;
  return 0;
error:

  if(header != NULL)
    free(header);

  if(echo_request_msg != NULL)
    free(echo_request_msg);

  if(*msg != NULL)
    free(*msg);

  //LOG_E(MAC, "%s: an error occured\n", __FUNCTION__);
  return -1;
}


int flexran_agent_destroy_echo_request(Protocol__FlexranMessage *msg) {
  if(msg->msg_case != PROTOCOL__FLEXRAN_MESSAGE__MSG_ECHO_REQUEST_MSG)
    goto error;

  free(msg->echo_request_msg->header);
  free(msg->echo_request_msg);
  free(msg);
  return 0;
error:
  LOG_E(FLEXRAN_AGENT, "%s: an error occured\n", __FUNCTION__);
  return -1;
}



int flexran_agent_echo_reply(mid_t mod_id, const void *params, Protocol__FlexranMessage **msg) {
  xid_t xid;
  Protocol__FlexHeader *header = NULL;
  Protocol__FlexranMessage *input = (Protocol__FlexranMessage *)params;
  Protocol__FlexEchoRequest *echo_req = input->echo_request_msg;
  xid = (echo_req->header)->xid;
  Protocol__FlexEchoReply *echo_reply_msg = NULL;
  echo_reply_msg = malloc(sizeof(Protocol__FlexEchoReply));

  if(echo_reply_msg == NULL)
    goto error;

  protocol__flex_echo_reply__init(echo_reply_msg);

  if (flexran_create_header(xid, PROTOCOL__FLEX_TYPE__FLPT_ECHO_REPLY, &header) != 0)
    goto error;

  echo_reply_msg->header = header;
  *msg = malloc(sizeof(Protocol__FlexranMessage));

  if(*msg == NULL)
    goto error;

  protocol__flexran_message__init(*msg);
  (*msg)->msg_case = PROTOCOL__FLEXRAN_MESSAGE__MSG_ECHO_REPLY_MSG;
  (*msg)->msg_dir = PROTOCOL__FLEXRAN_DIRECTION__SUCCESSFUL_OUTCOME;
  (*msg)->has_msg_dir = 1;
  (*msg)->echo_reply_msg = echo_reply_msg;
  return 0;
error:

  if(header != NULL)
    free(header);

  if(echo_reply_msg != NULL)
    free(echo_reply_msg);

  if(*msg != NULL)
    free(*msg);

  LOG_E(FLEXRAN_AGENT, "%s: an error occured\n", __FUNCTION__);
  return -1;
}


int flexran_agent_destroy_echo_reply(Protocol__FlexranMessage *msg) {
  if(msg->msg_case != PROTOCOL__FLEXRAN_MESSAGE__MSG_ECHO_REPLY_MSG)
    goto error;

  free(msg->echo_reply_msg->header);
  free(msg->echo_reply_msg);
  free(msg);
  return 0;
error:
  LOG_E(FLEXRAN_AGENT, "%s: an error occured\n", __FUNCTION__);
  return -1;
}


int flexran_agent_destroy_enb_config_reply(Protocol__FlexranMessage *msg) {
  if(msg->msg_case != PROTOCOL__FLEXRAN_MESSAGE__MSG_ENB_CONFIG_REPLY_MSG)
    goto error;

  free(msg->enb_config_reply_msg->header);
  Protocol__FlexEnbConfigReply *reply = msg->enb_config_reply_msg;

  for (int i = 0; i < reply->n_cell_config; i++) {
    if (reply->cell_config[i]->mbsfn_subframe_config_rfoffset)
      free(reply->cell_config[i]->mbsfn_subframe_config_rfoffset);

    if (reply->cell_config[i]->mbsfn_subframe_config_rfperiod)
      free(reply->cell_config[i]->mbsfn_subframe_config_rfperiod);

    if (reply->cell_config[i]->mbsfn_subframe_config_sfalloc)
      free(reply->cell_config[i]->mbsfn_subframe_config_sfalloc);

    /* si_config is shared between MAC and RRC, free here */
    if (reply->cell_config[i]->si_config) {
      for(int j = 0; j < reply->cell_config[i]->si_config->n_si_message; j++) {
        free(reply->cell_config[i]->si_config->si_message[j]);
      }

      free(reply->cell_config[i]->si_config->si_message);
      free(reply->cell_config[i]->si_config);
    }

    if (reply->cell_config[i]->slice_config) {
      flexran_agent_destroy_mac_slice_config(reply->cell_config[i]);
    }

    free(reply->cell_config[i]);
  }
  
  if (reply->s1ap)
    flexran_agent_free_s1ap_cell_config(&reply->s1ap);

  if (reply->loadedapps)
    free(reply->loadedapps);

  if (reply->loadedmacobjects)
    free(reply->loadedmacobjects);

  free(reply->cell_config);
  free(reply);
  free(msg);
  return 0;
error:
  //LOG_E(FLEXRAN_AGENT, "%s: an error occured\n", __FUNCTION__);
  return -1;
}

int flexran_agent_destroy_ue_config_reply(Protocol__FlexranMessage *msg) {
  if(msg->msg_case != PROTOCOL__FLEXRAN_MESSAGE__MSG_UE_CONFIG_REPLY_MSG)
    goto error;

  free(msg->ue_config_reply_msg->header);
  int i;
  Protocol__FlexUeConfigReply *reply = msg->ue_config_reply_msg;

  for(i = 0; i < reply->n_ue_config; i++) {
    free(reply->ue_config[i]->capabilities);
    free(reply->ue_config[i]);
  }

  free(reply->ue_config);
  free(reply);
  free(msg);
  return 0;
error:
  //LOG_E(FLEXRAN_AGENT, "%s: an error occured\n", __FUNCTION__);
  return -1;
}

int flexran_agent_destroy_lc_config_reply(Protocol__FlexranMessage *msg) {
  if(msg->msg_case != PROTOCOL__FLEXRAN_MESSAGE__MSG_LC_CONFIG_REPLY_MSG)
    goto error;

  int i, j;
  free(msg->lc_config_reply_msg->header);

  for (i = 0; i < msg->lc_config_reply_msg->n_lc_ue_config; i++) {
    for (j = 0; j < msg->lc_config_reply_msg->lc_ue_config[i]->n_lc_config; j++) {
      free(msg->lc_config_reply_msg->lc_ue_config[i]->lc_config[j]);
    }

    free(msg->lc_config_reply_msg->lc_ue_config[i]->lc_config);
    free(msg->lc_config_reply_msg->lc_ue_config[i]);
  }

  free(msg->lc_config_reply_msg->lc_ue_config);
  free(msg->lc_config_reply_msg);
  free(msg);
  return 0;
error:
  //LOG_E(FLEXRAN_AGENT, "%s: an error occured\n", __FUNCTION__);
  return -1;
}


int flexran_agent_destroy_enb_config_request(Protocol__FlexranMessage *msg) {
  if(msg->msg_case != PROTOCOL__FLEXRAN_MESSAGE__MSG_ENB_CONFIG_REQUEST_MSG)
    goto error;

  free(msg->enb_config_request_msg->header);
  free(msg->enb_config_request_msg);
  free(msg);
  return 0;
error:
  //LOG_E(FLEXRAN_AGENT, "%s: an error occured\n", __FUNCTION__);
  return -1;
}

int flexran_agent_destroy_ue_config_request(Protocol__FlexranMessage *msg) {
  /* TODO: Deallocate memory for a dynamically allocated UE config message */
  return 0;
}

int flexran_agent_destroy_lc_config_request(Protocol__FlexranMessage *msg) {
  /* TODO: Deallocate memory for a dynamically allocated LC config message */
  return 0;
}

// call this function to start a nanosecond-resolution timer
struct timespec timer_start(void) {
  struct timespec start_time;
  clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &start_time);
  return start_time;
}

// call this function to end a timer, returning nanoseconds elapsed as a long
long timer_end(struct timespec start_time) {
  struct timespec end_time;
  clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end_time);
  long diffInNanos = end_time.tv_nsec - start_time.tv_nsec;
  return diffInNanos;
}

int flexran_agent_control_delegation(mid_t mod_id, const void *params, Protocol__FlexranMessage **msg) {
  Protocol__FlexranMessage *input = (Protocol__FlexranMessage *)params;
  Protocol__FlexControlDelegation *control_delegation_msg = input->control_delegation_msg;
  *msg = NULL;

  char target[512];
  int len = snprintf(target, sizeof(target), "%s/libflex.%s.so",
                     RC.flexran[mod_id]->cache_name,
                     control_delegation_msg->name);
  if (len >= sizeof(target)) {
    LOG_E(FLEXRAN_AGENT, "target has been truncated, cannot write file name\n");
    return 0;
  }

  if (control_delegation_msg->has_payload) {
    /* use low-level API: check whether exists while creating so we can abort if
     * it exists to not overwrite anything */
    int fd = open(target, O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR);
    if (fd >= 0) {
      ssize_t l = write(fd,
                        control_delegation_msg->payload.data,
                        control_delegation_msg->payload.len);
      close(fd);
      if (l < control_delegation_msg->payload.len) {
        LOG_E(FLEXRAN_AGENT,
              "could not write complete control delegation to %s: only %ld out of "
              "%ld bytes\n",
              target,
              l,
              control_delegation_msg->payload.len);
        return 0;
      } else if (l < 0) {
        LOG_E(FLEXRAN_AGENT, "can not write control delegation data to %s: %s\n",
              target, strerror(errno));
        return 0;
      }
      LOG_I(FLEXRAN_AGENT, "wrote shared object %s\n", target);
    } else {
      if (errno == EEXIST) {
        LOG_W(FLEXRAN_AGENT, "file %s already exists, remove it first\n", target);
      } else {
        LOG_E(FLEXRAN_AGENT, "can not write control delegation data to %s: %s\n",
              target, strerror(errno));
      }
      return 0;
    }
  } else {
    LOG_W(FLEXRAN_AGENT, "remove file %s\n", target);
    int rc = remove(target);
    if (rc < 0)
      LOG_E(FLEXRAN_AGENT, "cannot remove file %s: %s\n", target, strerror(errno));
  }

  if (control_delegation_msg->has_delegation_type
      && control_delegation_msg->delegation_type == PROTOCOL__FLEX_CONTROL_DELEGATION_TYPE__FLCDT_MAC_DL_UE_SCHEDULER
      && control_delegation_msg->header
      && control_delegation_msg->header->has_xid) {
    /* Inform the MAC subsystem that a control delegation for it has arrived */
    /* TODO this should be triggered by an agent reconfiguration? */
    flexran_agent_mac_inform_delegation(mod_id, control_delegation_msg);
  }

  return 0;
}

int flexran_agent_destroy_control_delegation(Protocol__FlexranMessage *msg) {
  /*TODO: Dealocate memory for a dynamically allocated control delegation message*/
  return 0;
}

int flexran_agent_map_name_to_delegated_object(mid_t mod_id, const char *name,
    char *path, int maxlen) {
  int len = snprintf(path, maxlen, "%s/libflex.%s.so",
                     RC.flexran[mod_id]->cache_name, name);
  if (len >= maxlen) {
    LOG_E(FLEXRAN_AGENT, "path has been truncated, cannot read object\n");
    return -1;
  }

  struct stat buf;
  int status = stat(path, &buf);
  if (status < 0) {
    LOG_E(FLEXRAN_AGENT, "Could not stat object %s: %s\n", path, strerror(errno));
    return -1;
  }
  return 0;
}

int flexran_agent_reconfiguration(mid_t mod_id, const void *params, Protocol__FlexranMessage **msg) {
  Protocol__FlexranMessage *input = (Protocol__FlexranMessage *)params;
  Protocol__FlexAgentReconfiguration *agent_reconfiguration_msg = input->agent_reconfiguration_msg;
  if (agent_reconfiguration_msg->policy) {
    /* for compatibility: call old YAML configuration code, although we don't
     * use it anymore */
    apply_reconfiguration_policy(mod_id,
                                 agent_reconfiguration_msg->policy,
                                 strlen(agent_reconfiguration_msg->policy));
  }
  for (int i = 0; i < agent_reconfiguration_msg->n_systems; ++i) {
    const Protocol__FlexAgentReconfigurationSystem *sys = agent_reconfiguration_msg->systems[i];
    if (strcmp(sys->system, "app") == 0) {
      flexran_agent_handle_apps(mod_id, sys->subsystems, sys->n_subsystems);
    } else {
      LOG_E(FLEXRAN_AGENT,
            "unknown system name %s in flex_agent_reconfiguration message\n",
            sys->system);
    }
  }
  *msg = NULL;
  return 0;
}

int flexran_agent_destroy_agent_reconfiguration(Protocol__FlexranMessage *msg) {
  /*TODO: Dealocate memory for a dynamically allocated agent reconfiguration message*/
  return 0;
}

int flexran_agent_destroy_control_delegation_request(Protocol__FlexranMessage *msg) {
  if(msg->msg_case != PROTOCOL__FLEXRAN_MESSAGE__MSG_CONTROL_DEL_REQ_MSG)
    return -1;

  free(msg->control_del_req_msg->header);
  free(msg->control_del_req_msg->name);
  free(msg);
  return 0;
}

int flexran_agent_lc_config_reply(mid_t mod_id, const void *params, Protocol__FlexranMessage **msg) {
  xid_t xid;
  Protocol__FlexHeader *header = NULL;
  Protocol__FlexranMessage *input = (Protocol__FlexranMessage *)params;
  Protocol__FlexLcConfigRequest *lc_config_request_msg = input->lc_config_request_msg;
  xid = (lc_config_request_msg->header)->xid;
  Protocol__FlexLcConfigReply *lc_config_reply_msg;
  lc_config_reply_msg = malloc(sizeof(Protocol__FlexLcConfigReply));

  if(lc_config_reply_msg == NULL)
    goto error;

  protocol__flex_lc_config_reply__init(lc_config_reply_msg);

  if(flexran_create_header(xid, PROTOCOL__FLEX_TYPE__FLPT_GET_LC_CONFIG_REPLY, &header) != 0)
    goto error;

  lc_config_reply_msg->header = header;
  /* the lc_config_reply entirely depends on MAC except for the
   * mac_eNB_get_rrc_status() function (which in the current OAI implementation
   * is reachable if F1 is present). Therefore we check here wether MAC CM is
   * present and the message gets properly filled if it is or remains empty if
   * not */
  lc_config_reply_msg->n_lc_ue_config =
    flexran_agent_get_mac_xface(mod_id) ? flexran_get_mac_num_ues(mod_id) : 0;
  Protocol__FlexLcUeConfig **lc_ue_config = NULL;

  if (lc_config_reply_msg->n_lc_ue_config > 0) {
    lc_ue_config = malloc(sizeof(Protocol__FlexLcUeConfig *) * lc_config_reply_msg->n_lc_ue_config);

    if (lc_ue_config == NULL) {
      goto error;
    }

    // Fill the config for each UE
    for (int i = 0; i < lc_config_reply_msg->n_lc_ue_config; i++) {
      lc_ue_config[i] = malloc(sizeof(Protocol__FlexLcUeConfig));
      if (!lc_ue_config[i]){
        for (int j = 0; j < i; j++){
          free(lc_ue_config[j]);
        }
        free(lc_ue_config);
        goto error;
      }

      protocol__flex_lc_ue_config__init(lc_ue_config[i]);
      const int UE_id = flexran_get_mac_ue_id(mod_id, i);
      flexran_agent_fill_mac_lc_ue_config(mod_id, UE_id, lc_ue_config[i]);
    } // end for UE

    lc_config_reply_msg->lc_ue_config = lc_ue_config;
  } // lc_config_reply_msg->n_lc_ue_config > 0

  *msg = malloc(sizeof(Protocol__FlexranMessage));

  if (*msg == NULL){
    for (int k = 0; k < lc_config_reply_msg->n_lc_ue_config; k++){
      free(lc_ue_config[k]);
    }
    free(lc_ue_config);
    goto error;
  }

  protocol__flexran_message__init(*msg);
  (*msg)->msg_case = PROTOCOL__FLEXRAN_MESSAGE__MSG_LC_CONFIG_REPLY_MSG;
  (*msg)->msg_dir = PROTOCOL__FLEXRAN_DIRECTION__SUCCESSFUL_OUTCOME;
  (*msg)->lc_config_reply_msg = lc_config_reply_msg;
  return 0;
error:

  // TODO: Need to make proper error handling
  if (header != NULL)
    free(header);

  if (lc_config_reply_msg != NULL)
    free(lc_config_reply_msg);

  if(*msg != NULL)
    free(*msg);

  //LOG_E(FLEXRAN_AGENT, "%s: an error occured\n", __FUNCTION__);
  return -1;
}

/*
 * ************************************
 * UE Configuration Reply
 * ************************************
 */

int sort_ue_config(const void *a, const void *b) {
  const Protocol__FlexUeConfig *fa = a;
  const Protocol__FlexUeConfig *fb = b;

  if (fa->rnti < fb->rnti)
    return -1;
  else if (fa->rnti < fb->rnti)
    return 1;

  return 0;
}

int flexran_agent_ue_config_reply(mid_t mod_id, const void *params, Protocol__FlexranMessage **msg) {
  xid_t xid;
  Protocol__FlexHeader *header = NULL;
  Protocol__FlexranMessage *input = (Protocol__FlexranMessage *)params;
  Protocol__FlexUeConfigRequest *ue_config_request_msg = input->ue_config_request_msg;
  xid = (ue_config_request_msg->header)->xid;
  Protocol__FlexUeConfigReply *ue_config_reply_msg;
  ue_config_reply_msg = malloc(sizeof(Protocol__FlexUeConfigReply));

  if(ue_config_reply_msg == NULL)
    goto error;

  protocol__flex_ue_config_reply__init(ue_config_reply_msg);

  if(flexran_create_header(xid, PROTOCOL__FLEX_TYPE__FLPT_GET_UE_CONFIG_REPLY, &header) != 0)
    goto error;

  ue_config_reply_msg->header = header;
  ue_config_reply_msg->n_ue_config = flexran_agent_get_num_ues(mod_id);

  Protocol__FlexUeConfig **ue_config;

  if (ue_config_reply_msg->n_ue_config > 0) {
    ue_config = malloc(sizeof(Protocol__FlexUeConfig *) * ue_config_reply_msg->n_ue_config);

    if (ue_config == NULL) {
      goto error;
    }

    rnti_t rntis[ue_config_reply_msg->n_ue_config];
    flexran_get_rrc_rnti_list(mod_id, rntis, ue_config_reply_msg->n_ue_config);

    for (int i = 0; i < ue_config_reply_msg->n_ue_config; i++) {
      const rnti_t rnti = rntis[i];
      ue_config[i] = malloc(sizeof(Protocol__FlexUeConfig));
      protocol__flex_ue_config__init(ue_config[i]);

      if (flexran_agent_get_rrc_xface(mod_id))
        flexran_agent_fill_rrc_ue_config(mod_id, rnti, ue_config[i]);

      if (flexran_agent_get_mac_xface(mod_id)) {
        const int UE_id = flexran_get_mac_ue_id_rnti(mod_id, rnti);
        flexran_agent_fill_mac_ue_config(mod_id, UE_id, ue_config[i]);
      }
    }

    ue_config_reply_msg->ue_config = ue_config;
  }

  *msg = malloc(sizeof(Protocol__FlexranMessage));

  if (*msg == NULL)
    goto error;

  protocol__flexran_message__init(*msg);
  (*msg)->msg_case = PROTOCOL__FLEXRAN_MESSAGE__MSG_UE_CONFIG_REPLY_MSG;
  (*msg)->msg_dir = PROTOCOL__FLEXRAN_DIRECTION__SUCCESSFUL_OUTCOME;
  (*msg)->ue_config_reply_msg = ue_config_reply_msg;
  return 0;
error:

  // TODO: Need to make proper error handling
  if (header != NULL)
    free(header);

  if (ue_config_reply_msg != NULL)
    free(ue_config_reply_msg);

  if(*msg != NULL)
    free(*msg);

  //LOG_E(FLEXRAN_AGENT, "%s: an error occured\n", __FUNCTION__);
  return -1;
}


/*
 * ************************************
 * eNB Configuration Request and Reply
 * ************************************
 */

int flexran_agent_enb_config_request(mid_t mod_id, const void *params, Protocol__FlexranMessage **msg) {
  Protocol__FlexHeader *header = NULL;
  xid_t xid = 1;
  Protocol__FlexEnbConfigRequest *enb_config_request_msg;
  enb_config_request_msg = malloc(sizeof(Protocol__FlexEnbConfigRequest));

  if(enb_config_request_msg == NULL)
    goto error;

  protocol__flex_enb_config_request__init(enb_config_request_msg);

  if(flexran_create_header(xid,PROTOCOL__FLEX_TYPE__FLPT_GET_ENB_CONFIG_REQUEST, &header) != 0)
    goto error;

  enb_config_request_msg->header = header;
  *msg = malloc(sizeof(Protocol__FlexranMessage));

  if(*msg == NULL)
    goto error;

  protocol__flexran_message__init(*msg);
  (*msg)->msg_case = PROTOCOL__FLEXRAN_MESSAGE__MSG_ENB_CONFIG_REQUEST_MSG;
  (*msg)->msg_dir = PROTOCOL__FLEXRAN_DIRECTION__INITIATING_MESSAGE;
  (*msg)->enb_config_request_msg = enb_config_request_msg;
  return 0;
error:

  // TODO: Need to make proper error handling
  if (header != NULL)
    free(header);

  if (enb_config_request_msg != NULL)
    free(enb_config_request_msg);

  if(*msg != NULL)
    free(*msg);

  //LOG_E(MAC, "%s: an error occured\n", __FUNCTION__);
  return -1;
}

int flexran_agent_enb_config_reply(mid_t mod_id, const void *params, Protocol__FlexranMessage **msg) {
  xid_t xid;
  Protocol__FlexHeader *header = NULL;
  Protocol__FlexranMessage *input = (Protocol__FlexranMessage *)params;
  Protocol__FlexEnbConfigRequest *enb_config_req_msg = input->enb_config_request_msg;
  xid = (enb_config_req_msg->header)->xid;
  Protocol__FlexEnbConfigReply *enb_config_reply_msg;
  enb_config_reply_msg = malloc(sizeof(Protocol__FlexEnbConfigReply));

  if(enb_config_reply_msg == NULL)
    goto error;

  protocol__flex_enb_config_reply__init(enb_config_reply_msg);

  if(flexran_create_header(xid, PROTOCOL__FLEX_TYPE__FLPT_GET_ENB_CONFIG_REPLY, &header) != 0)
    goto error;

  enb_config_reply_msg->header = header;
  enb_config_reply_msg->n_cell_config = MAX_NUM_CCs;
  Protocol__FlexCellConfig **cell_conf;

  if(enb_config_reply_msg->n_cell_config > 0) {
    cell_conf = malloc(sizeof(Protocol__FlexCellConfig *) * enb_config_reply_msg->n_cell_config);

    if(cell_conf == NULL)
      goto error;

    for(int i = 0; i < enb_config_reply_msg->n_cell_config; i++) {
      cell_conf[i] = malloc(sizeof(Protocol__FlexCellConfig));

      if (!cell_conf[i]) {
        for (int j = 0; j < i; j++) {
          free(cell_conf[j]);
        }
        free(cell_conf);
        goto error;
      }

      protocol__flex_cell_config__init(cell_conf[i]);

      if (flexran_agent_get_phy_xface(mod_id))
        flexran_agent_fill_phy_cell_config(mod_id, i, cell_conf[i]);

      if (flexran_agent_get_rrc_xface(mod_id))
        flexran_agent_fill_rrc_cell_config(mod_id, i, cell_conf[i]);

      if (flexran_agent_get_mac_xface(mod_id))
        flexran_agent_fill_mac_cell_config(mod_id, i, cell_conf[i]);

      cell_conf[i]->carrier_index = i;
      cell_conf[i]->has_carrier_index = 1;
    }
    
    enb_config_reply_msg->cell_config=cell_conf;
  }
  
  if (flexran_agent_get_s1ap_xface(mod_id))
    flexran_agent_fill_s1ap_cell_config(mod_id, &enb_config_reply_msg->s1ap);

  flexran_agent_fill_loaded_apps(mod_id, enb_config_reply_msg);

  flexran_agent_mac_fill_loaded_mac_objects(mod_id, enb_config_reply_msg);

  *msg = malloc(sizeof(Protocol__FlexranMessage));

  if(*msg == NULL) {
    for (int k = 0; k < enb_config_reply_msg->n_cell_config; k++) {
      free(cell_conf[k]);
    }
    free(cell_conf);
    goto error;
  }

  protocol__flexran_message__init(*msg);
  (*msg)->msg_case = PROTOCOL__FLEXRAN_MESSAGE__MSG_ENB_CONFIG_REPLY_MSG;
  (*msg)->msg_dir = PROTOCOL__FLEXRAN_DIRECTION__SUCCESSFUL_OUTCOME;
  (*msg)->enb_config_reply_msg = enb_config_reply_msg;
  return 0;
error:

  // TODO: Need to make proper error handling
  if (header != NULL)
    free(header);

  if (enb_config_reply_msg != NULL)
    free(enb_config_reply_msg);

  if(*msg != NULL)
    free(*msg);

  //LOG_E(FLEXRAN_AGENT, "%s: an error occured\n", __FUNCTION__);
  return -1;
}


int flexran_agent_rrc_reconfiguration(mid_t mod_id, const void *params, Protocol__FlexranMessage **msg) {
  Protocol__FlexranMessage *input = (Protocol__FlexranMessage *)params;
  Protocol__FlexRrcTriggering *triggering = input->rrc_triggering;
  // Set the proper values using FlexRAN API (protected with mutex ?)
  if (!flexran_agent_get_rrc_xface(mod_id)) {
    LOG_E(FLEXRAN_AGENT, "%s(): no RRC present, aborting\n", __func__);
    return -1;
  }

  int num_ue = flexran_get_rrc_num_ues(mod_id);
  if (num_ue == 0)
    return 0;

  rnti_t rntis[num_ue];
  flexran_get_rrc_rnti_list(mod_id, rntis, num_ue);
  for (int i = 0; i < num_ue; i++) {
    const rnti_t rnti = rntis[i];
    const int error = update_rrc_reconfig(mod_id, rnti, triggering);
    if (error < 0) {
      LOG_E(FLEXRAN_AGENT, "Error in updating user %d\n", i);
      continue;
    }
    // Call the proper wrapper in FlexRAN API
    if (flexran_call_rrc_reconfiguration (mod_id, rnti) < 0) {
      LOG_E(FLEXRAN_AGENT, "Error in reconfiguring user %d\n", i);
    }
  }

  *msg = NULL;
  return 0;
}

int flexran_agent_rrc_trigger_handover(mid_t mod_id, const void *params, Protocol__FlexranMessage **msg) {
  Protocol__FlexranMessage *input = (Protocol__FlexranMessage *)params;
  Protocol__FlexHoCommand *ho_command = input->ho_command_msg;

  int rnti_found = 0;

  // Set the proper values using FlexRAN API (protected with mutex ?)
  if (!flexran_agent_get_rrc_xface(mod_id)) {
    LOG_E(FLEXRAN_AGENT, "%s(): no RRC present, aborting\n", __func__);
    return -1;
  }

  int num_ue = flexran_get_rrc_num_ues(mod_id);
  if (num_ue == 0)
    return 0;

  if (!ho_command->has_rnti) {
    LOG_E(FLEXRAN_AGENT, "%s(): no UE rnti is present, aborting\n", __func__);
    return -1;
  }

  if (!ho_command->has_target_phy_cell_id) {
    LOG_E(FLEXRAN_AGENT, "%s(): no target physical cell id is  present, aborting\n", __func__);
    return -1;
  }

  rnti_t rntis[num_ue];
  flexran_get_rrc_rnti_list(mod_id, rntis, num_ue);
  for (int i = 0; i < num_ue; i++) {
    const rnti_t rnti = rntis[i];
    if (ho_command->rnti == rnti) {
      rnti_found = 1;
      // Call the proper wrapper in FlexRAN API
      if (flexran_call_rrc_trigger_handover(mod_id, ho_command->rnti, ho_command->target_phy_cell_id) < 0) {
        LOG_E(FLEXRAN_AGENT, "Error in handovering user %d/RNTI %x\n", i, rnti);
      }
      break;
    }
  }

  if (!rnti_found)
    return -1;

  *msg = NULL;
  return 0;
}

int flexran_agent_destroy_rrc_reconfiguration(Protocol__FlexranMessage *msg) {
  // TODO
  return 0;
}

int flexran_agent_destroy_rrc_trigger_handover(Protocol__FlexranMessage *msg) {
  // TODO
  return 0;
}

int flexran_agent_handle_enb_config_reply(mid_t mod_id, const void *params, Protocol__FlexranMessage **msg) {
  Protocol__FlexranMessage *input = (Protocol__FlexranMessage *)params;
  Protocol__FlexEnbConfigReply *enb_config = input->enb_config_reply_msg;

  if (enb_config->n_cell_config > 1)
    LOG_W(FLEXRAN_AGENT, "ignoring slice configs for other cell except cell 0\n");

  if (enb_config->n_cell_config > 0) {
    if (flexran_agent_get_mac_xface(mod_id) && enb_config->cell_config[0]->slice_config) {
      prepare_update_slice_config(mod_id,
                                  &enb_config->cell_config[0]->slice_config,
                                  1 /* request objects if necessary */);
    }
    if (enb_config->cell_config[0]->has_eutra_band
        && enb_config->cell_config[0]->has_dl_freq
        && enb_config->cell_config[0]->has_ul_freq
        && enb_config->cell_config[0]->has_dl_bandwidth) {
      initiate_soft_restart(mod_id, enb_config->cell_config[0]);
    }
    if (flexran_agent_get_rrc_xface(mod_id)
        && enb_config->cell_config[0]->has_x2_ho_net_control) {
      LOG_I(FLEXRAN_AGENT,
            "setting X2 HO NetControl to %d\n",
            enb_config->cell_config[0]->x2_ho_net_control);
      const int rc = flexran_set_x2_ho_net_control(mod_id, enb_config->cell_config[0]->x2_ho_net_control);
      if (rc < 0)
        LOG_E(FLEXRAN_AGENT, "Error in configuring X2 handover controlled by network");
    }
    if (flexran_agent_get_rrc_xface(mod_id) && enb_config->cell_config[0]->n_plmn_id > 0) {
      flexran_agent_handle_plmn_update(mod_id,
                                       0,
                                       enb_config->cell_config[0]->n_plmn_id,
                                       enb_config->cell_config[0]->plmn_id);
    }
  }

  if (flexran_agent_get_s1ap_xface(mod_id) && enb_config->s1ap) {
    flexran_agent_handle_mme_update(mod_id,
                                    enb_config->s1ap->n_mme,
                                    enb_config->s1ap->mme);
  }

  *msg = NULL;
  return 0;
}

int flexran_agent_handle_ue_config_reply(mid_t mod_id, const void *params, Protocol__FlexranMessage **msg) {
  int i;
  Protocol__FlexranMessage *input = (Protocol__FlexranMessage *)params;
  Protocol__FlexUeConfigReply *ue_config_reply = input->ue_config_reply_msg;

  for (i = 0; flexran_agent_get_mac_xface(mod_id) && i < ue_config_reply->n_ue_config; i++)
    prepare_ue_slice_assoc_update(mod_id, &ue_config_reply->ue_config[i]);
  /* prepare_ue_slice_assoc_update takes ownership of the individual
   * FlexUeConfig messages. Therefore, mark zero messages to not accidentally
   * free them twice */
  ue_config_reply->n_ue_config = 0;

  *msg = NULL;
  return 0;
}

int flexran_agent_get_num_ues(mid_t mod_id) {
  const int has_rrc = flexran_agent_get_rrc_xface(mod_id) != NULL;
  const int has_mac = flexran_agent_get_mac_xface(mod_id) != NULL;
  DevAssert(has_rrc || has_mac);
  if (has_rrc && !has_mac)
    return flexran_get_rrc_num_ues(mod_id);
  if (!has_rrc && has_mac)
    return flexran_get_mac_num_ues(mod_id);

  /* has both */
  const int nrrc = flexran_get_rrc_num_ues(mod_id);
  const int nmac = flexran_get_mac_num_ues(mod_id);
  if (nrrc != nmac) {
    const int n_ue = nrrc < nmac ? nrrc : nmac;
    LOG_E(FLEXRAN_AGENT, "%s(): different numbers of UEs in RRC (%d) and MAC (%d), reporting for %d UEs\n",
          __func__, nrrc, nmac, n_ue);
    return n_ue;
  }
  return nrrc;
}
