/*******************************************************************************
OpenAirInterface
Copyright(c) 1999 - 2014 Eurecom
OpenAirInterface is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenAirInterface 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 OpenAirInterface.The full GNU General Public License is
included in this distribution in the file called "COPYING". If not,
see .
Contact Information
OpenAirInterface Admin: openair_admin@eurecom.fr
OpenAirInterface Tech : openair_tech@eurecom.fr
OpenAirInterface Dev : openair4g-devel@eurecom.fr
Address : Eurecom, Compus SophiaTech 450, route des chappes, 06451 Biot, France.
*******************************************************************************/
/*****************************************************************************
Source user_api.c
Version 0.1
Date 2012/02/28
Product NAS stack
Subsystem Application Programming Interface
Author Frederic Maurel
Description Implements the API used by the NAS layer running in the UE
to send/receive message to/from the user application layer
*****************************************************************************/
#include "user_api.h"
#include "nas_log.h"
#include "socket.h"
#include "device.h"
#include "nas_user.h"
#include "at_command.h"
#include "at_response.h"
#include "at_error.h"
#include "user_indication.h"
#include // strerror, memset
#include // gai_strerror
#include // errno
#include // sprintf
#include // gethostname
/****************************************************************************/
/**************** E X T E R N A L D E F I N I T I O N S ****************/
/****************************************************************************/
/****************************************************************************/
/******************* L O C A L D E F I N I T I O N S *******************/
/****************************************************************************/
/*
* Asynchronous notification procedure handlers
*/
static int _user_api_registration_handler(unsigned char id, const void* data, size_t size);
static int _user_api_location_handler(unsigned char id, const void* data, size_t size);
static int _user_api_network_handler(unsigned char id, const void* data, size_t size);
static int _user_api_pdn_connection_handler(unsigned char id, const void* data, size_t size);
static int _user_api_send(at_response_t* data);
/* -------------------
* Connection endpoint
* -------------------
* The connection endpoint is used to send/receive data to/from the
* user application layer. Its definition depends on the underlaying
* mechanism chosen to communicate (network socket, I/O terminal device).
* A connection endpoint is handled using an identifier, and functions
* used to retreive the file descriptor actually allocated by the system,
* to receive data, to send data, and to perform clean up when connection
* is shut down.
* Only one single end to end connection with the user is managed at a
* time.
*/
static struct {
/* Connection endpoint reference */
void* endpoint;
/* Connection endpoint handlers */
void* (*open) (int, const char*, const char*);
int (*getfd)(const void*);
ssize_t (*recv) (void*, char*, size_t);
ssize_t (*send) (const void*, const char*, size_t);
void (*close)(void*);
} _user_api_id;
#define USER_API_OPEN(a, b, c) _user_api_id.open(a, b, c)
#define USER_API_GETFD() _user_api_id.getfd(_user_api_id.endpoint)
#define USER_API_RECV(a, b) _user_api_id.recv(_user_api_id.endpoint, a, b)
#define USER_API_SEND(a, b) _user_api_id.send(_user_api_id.endpoint, a, b)
#define USER_API_CLOSE() _user_api_id.close(_user_api_id.endpoint)
/*
* The buffer used to receive data from the user application layer
*/
#define USER_API_RECV_BUFFER_SIZE 4096
static char _user_api_recv_buffer[USER_API_RECV_BUFFER_SIZE];
/*
* The buffer used to send data to the user application layer
*/
#define USER_API_SEND_BUFFER_SIZE USER_API_RECV_BUFFER_SIZE
static char _user_api_send_buffer[USER_API_SEND_BUFFER_SIZE];
/*
* The decoded data received from the user application layer
*/
static struct {
int n_cmd; /* number of user data to be processed */
#define USER_DATA_MAX 10
at_command_t cmd[USER_DATA_MAX]; /* user data to be processed */
} _user_data = {};
/****************************************************************************/
/****************** E X P O R T E D F U N C T I O N S ******************/
/****************************************************************************/
/****************************************************************************
** **
** Name: user_api_initialize() **
** **
** Description: Initializes the user API from which the NAS layer **
** will send/receive messages to/from the user application **
** layer **
** **
** Inputs: host: The name of the host from which the user **
** application layer will connect **
** port: The local port number **
** Others: None **
** **
** Outputs: Return: RETURNerror, RETURNok **
** Others: _user_api_id **
** **
***************************************************************************/
int user_api_initialize(const char* host, const char* port,
const char* devname, const char* devparams)
{
LOG_FUNC_IN;
gethostname(_user_api_send_buffer, USER_API_SEND_BUFFER_SIZE);
if (devname != NULL) {
/* Initialize device handlers */
_user_api_id.open = device_open;
_user_api_id.getfd = device_get_fd;
_user_api_id.recv = device_read;
_user_api_id.send = device_write;
_user_api_id.close = device_close;
/* Initialize communication channel */
_user_api_id.endpoint = USER_API_OPEN(DEVICE, devname, devparams);
if (_user_api_id.endpoint == NULL) {
LOG_TRACE(ERROR, "USR-API - Failed to open connection endpoint, "
"%s", strerror(errno));
LOG_FUNC_RETURN (RETURNerror);
}
LOG_TRACE(INFO, "USR-API - User's communication device %d is OPENED "
"on %s/%s", user_api_get_fd(), _user_api_send_buffer, devname);
} else {
/* Initialize network socket handlers */
_user_api_id.open = socket_udp_open;
_user_api_id.getfd = socket_get_fd;
_user_api_id.recv = socket_recv;
_user_api_id.send = socket_send;
_user_api_id.close = socket_close;
/* Initialize communication channel */
_user_api_id.endpoint = USER_API_OPEN(SOCKET_SERVER, host, port);
if (_user_api_id.endpoint == NULL) {
const char* error = ( (errno < 0) ?
gai_strerror(errno) : strerror(errno) );
LOG_TRACE(ERROR, "USR-API - Failed to open connection endpoint, "
"%s", error);
LOG_FUNC_RETURN (RETURNerror);
}
LOG_TRACE(INFO, "USR-API - User's UDP socket %d is BOUND to %s/%s",
user_api_get_fd(), _user_api_send_buffer, port);
}
/* Register the asynchronous notification handlers */
if (user_ind_register(USER_IND_REG, 0, _user_api_registration_handler) != RETURNok) {
LOG_TRACE(WARNING, "USR-API - "
"Failed to register registration notification");
} else if (user_ind_register(USER_IND_LOC, 0, _user_api_location_handler) != RETURNok) {
LOG_TRACE(WARNING, "USR-API - "
"Failed to register location notification");
} else if (user_ind_register(USER_IND_PLMN, 0, _user_api_network_handler) != RETURNok) {
LOG_TRACE(WARNING, "USR-API - "
"Failed to register network notification");
} else if (user_ind_register(USER_IND_PLMN, 0, NULL) != RETURNok) {
LOG_TRACE(WARNING, "USR-API - Failed to enable network notification");
} else if (user_ind_register(USER_IND_PDN, 0, _user_api_pdn_connection_handler) != RETURNok) {
LOG_TRACE(WARNING, "USR-API - "
"Failed to register PDN connection notification");
} else if (user_ind_register(USER_IND_PDN, AT_CGACT, NULL) != RETURNok) {
LOG_TRACE(WARNING, "USR-API - "
"Failed to enable PDN connection notification");
} else {
LOG_TRACE(INFO, "USR-API - "
"Notification handlers successfully registered");
}
LOG_FUNC_RETURN (RETURNok);
}
/****************************************************************************
** **
** Name: user_api_get_fd() **
** **
** Description: Get the file descriptor of the connection endpoint used **
** to send/receive messages to/from the user application **
** layer **
** **
** Inputs: None **
** Others: _user_api_id **
** **
** Outputs: Return: The file descriptor of the connection end- **
** point used by the user application layer **
** Others: None **
** **
***************************************************************************/
int user_api_get_fd(void)
{
LOG_FUNC_IN;
LOG_FUNC_RETURN (USER_API_GETFD());
}
/****************************************************************************
** **
** Name: user_api_get_data() **
** **
** Description: Get a generic pointer to the user data structure at the **
** given index. Casting to the proper type is necessary **
** before its usage. **
** **
** Inputs: index: Index of the user data structure to get **
** Others: _user_data **
** **
** Outputs: Return: A generic pointer to the user data **
** structure **
** Others: None **
** **
***************************************************************************/
const void* user_api_get_data(int index)
{
LOG_FUNC_IN;
if (index < _user_data.n_cmd) {
LOG_FUNC_RETURN ((void*)(&_user_data.cmd[index]));
}
LOG_FUNC_RETURN (NULL);
}
/****************************************************************************
** **
** Name: user_api_read_data() **
** **
** Description: Read data received from the user application layer **
** **
** Inputs: fd: File descriptor of the connection endpoint **
** from which data have been received **
** Others: _user_api_id **
** **
** Outputs: Return: The number of bytes read when success; **
** RETURNerror Otherwise **
** Others: _user_api_recv_buffer, _user_api_id **
** **
***************************************************************************/
int user_api_read_data(int fd)
{
LOG_FUNC_IN;
int rbytes;
/* Sanity check */
int sfd = user_api_get_fd();
if (fd != sfd) {
LOG_TRACE(ERROR, "USR-API - Endpoint %d is not the one created for communication with the user application layer (%d)", fd, sfd);
LOG_FUNC_RETURN (RETURNerror);
}
memset(_user_api_recv_buffer, 0, USER_API_RECV_BUFFER_SIZE);
/* Receive data from the user application layer */
rbytes = USER_API_RECV(_user_api_recv_buffer, USER_API_RECV_BUFFER_SIZE);
if (rbytes == RETURNerror) {
LOG_TRACE(ERROR, "USR-API - recv() failed, %s", strerror(errno));
LOG_FUNC_RETURN (RETURNerror);
} else if (rbytes == 0) {
//LOG_TRACE(WARNING, "USR-API - A signal was caught");
} else {
LOG_TRACE(INFO, "USR-API - %d bytes received "
"from the user application layer", rbytes);
LOG_DUMP(_user_api_recv_buffer, rbytes);
}
LOG_FUNC_RETURN (rbytes);
}
/****************************************************************************
** **
** Name: user_api_set_data() **
** **
** Description: Set content of data received buffer to allow loop back **
** **
** Inputs: message: Message to set into the received buffer **
** **
** Outputs: Return: The number of bytes write when success; **
** RETURNerror Otherwise **
** Others: _user_api_recv_buffer **
** **
***************************************************************************/
int user_api_set_data(char *message)
{
LOG_FUNC_IN;
int rbytes;
memset(_user_api_recv_buffer, 0, USER_API_RECV_BUFFER_SIZE);
strncpy(_user_api_recv_buffer, message, USER_API_RECV_BUFFER_SIZE);
rbytes = strlen(_user_api_recv_buffer);
LOG_TRACE(INFO, "USR-API - %d bytes write", rbytes);
LOG_DUMP(_user_api_recv_buffer, rbytes);
LOG_FUNC_RETURN (rbytes);
}
/****************************************************************************
** **
** Name: user_api_send_data() **
** **
** Description: Send data to the user application layer **
** **
** Inputs: fd: File descriptor of the connection endpoint **
** to which data have to be sent **
** length: Number of bytes to send **
** Others: _user_api_send_buffer, _user_api_id **
** **
** Outputs: Return: The number of bytes sent when success; **
** RETURNerror Otherwise **
** Others: None **
** **
***************************************************************************/
static int _user_api_send_data(int length)
{
int sbytes = USER_API_SEND(_user_api_send_buffer, length);
if (sbytes == RETURNerror) {
LOG_TRACE(ERROR, "USR-API - send() failed, %s", strerror(errno));
return RETURNerror;
} else if (sbytes == 0) {
LOG_TRACE(WARNING, "USR-API - A signal was caught");
} else {
LOG_TRACE(INFO, "USR-API - %d bytes sent "
"to the user application layer", sbytes);
LOG_DUMP(_user_api_send_buffer, sbytes);
}
return sbytes;
}
int user_api_send_data(int fd, int length)
{
LOG_FUNC_IN;
/* Sanity check */
int sfd = user_api_get_fd();
if (fd != sfd) {
LOG_TRACE(ERROR, "USR-API - Endpoint %d is not the one created for communication with the user application layer (%d)", fd, sfd);
LOG_FUNC_RETURN (RETURNerror);
}
/* Send data to the user application layer */
int sbytes = 0;
if (length > 0) {
sbytes = _user_api_send_data(length);
}
LOG_FUNC_RETURN (sbytes);
}
/****************************************************************************
** **
** Name: user_api_close() **
** **
** Description: Close the user API from which the NAS layer sent/received **
** messages to/from the user application layer **
** **
** Inputs: fd: File descriptor of the connection endpoint **
** allocated by the system to communicate **
** with the user application layer **
** Others: None **
** **
** Outputs: Return: None **
** Others: _user_api_id **
** **
***************************************************************************/
void user_api_close(int fd)
{
LOG_FUNC_IN;
/* Sanity check */
int sfd = user_api_get_fd();
if (fd != sfd) {
LOG_TRACE(ERROR, "USR-API - Endpoint %d is not the one created for communication with the user application layer (%d)", fd, sfd);
LOG_FUNC_OUT;
return;
}
/* Cleanup the connection endpoint */
USER_API_CLOSE();
_user_api_id.endpoint = NULL;
LOG_FUNC_OUT;
}
/****************************************************************************
** **
** Name: user_api_decode_data() **
** **
** Description: Parses the message received from the user application **
** (mainly AT commands) and fills the user data structure. **
** Returns an AT syntax error code to the user application **
** layer when the AT command failed to be decoded. **
** **
** Inputs: length: Number of bytes to decode **
** Others: _user_api_recv_buffer **
** **
** Outputs: Return: The number of AT commands succeessfully **
** decoded **
** Others: _user_api_send_buffer, _user_data **
** **
***************************************************************************/
int user_api_decode_data(int length)
{
LOG_FUNC_IN;
/* Parse the AT command line */
LOG_TRACE(INFO, "USR-API - Decode user data: %s", _user_api_recv_buffer);
_user_data.n_cmd = at_command_decode(_user_api_recv_buffer, length,
_user_data.cmd);
if (_user_data.n_cmd > 0) {
/* AT command data received from the user application layer
* has been successfully decoded */
LOG_TRACE(INFO, "USR-API - %d AT command%s ha%s been successfully "
"decoded", _user_data.n_cmd,
(_user_data.n_cmd > 1) ? "s" : "",
(_user_data.n_cmd > 1) ? "ve" : "s");
} else {
int bytes;
/* Failed to decode AT command data received from the user
* application layer; Return syntax error code message */
LOG_TRACE(ERROR, "USR-API - Syntax error: Failed to decode "
"AT command data %s", _user_api_recv_buffer);
/* Encode the syntax error code message */
bytes = at_error_encode(_user_api_send_buffer, AT_ERROR_SYNTAX,
AT_ERROR_OPERATION_NOT_SUPPORTED);
/* Send the syntax error code message */
(void) _user_api_send_data(bytes);
}
LOG_FUNC_RETURN (_user_data.n_cmd);
}
/****************************************************************************
** **
** Name: user_api_encode_data() **
** **
** Description: Encodes AT command response message **
** **
** Inputs data: Generic pointer to the data to encode **
** success_code: Indicates whether success code has to be **
** displayed or not (covers the case where **
** more than one AT command is executed in **
** the same user command line). **
** Others: None **
** **
** Outputs: Return: The number of characters that have been **
** successfully encoded; **
** RETURNerror otherwise. **
** Others: _user_api_send_buffer **
** **
***************************************************************************/
int user_api_encode_data(const void* data, int success_code)
{
LOG_FUNC_IN;
const at_response_t* user_data = (at_response_t*)(data);
int bytes;
/* Encode AT command error message */
if (user_data->cause_code != AT_ERROR_SUCCESS) {
bytes = at_error_encode(_user_api_send_buffer, AT_ERROR_CME,
user_data->cause_code);
}
/* Encode AT command response message */
else {
bytes = at_response_encode(_user_api_send_buffer, user_data);
/* Add success result code */
if ( (success_code) && (bytes != RETURNerror) ) {
bytes += at_error_encode(&_user_api_send_buffer[bytes],
AT_ERROR_OK, 0);
}
}
if (bytes != RETURNerror) {
LOG_TRACE(INFO, "USR-API - %d bytes encoded", bytes);
} else {
LOG_TRACE(ERROR, "USR-API - Syntax error: Failed to encode AT "
"response data (%d)", user_data->id);
}
LOG_FUNC_RETURN (bytes);
}
/****************************************************************************
** **
** Name: user_api_callback() **
** **
** Description: Notifies the user application that asynchronous notifica- **
** tion has been received from the EPS Mobility Management **
** sublayer. **
** **
** Inputs: stat: Network registration status **
** tac: Location/Tracking Area Code **
** ci: Indentifier of the serving cell **
** AcT: Access Technology supported by the cell **
** data: Data string to display to the user **
** size: Size of the notification data (only used **
** to display string information to the user **
** application) **
** Others: None **
** **
** Outputs: None **
** Return: RETURNok, RETURNerror **
** Others: None **
** **
***************************************************************************/
int user_api_emm_callback(Stat_t stat, tac_t tac, ci_t ci, AcT_t AcT,
const char* data, size_t size)
{
LOG_FUNC_IN;
int rc = RETURNok;
if (size > 1) {
/*
* The list of available operators present in the network has to be
* displayed to the user application
*/
rc = user_ind_notify(USER_IND_PLMN, (void*)data, size);
} else {
user_indication_t ind;
ind.notification.reg.status = stat;
if (size > 0) {
/* The UE's network registration status has changed */
rc = user_ind_notify(USER_IND_REG, (void*)&ind, 0);
}
if (rc != RETURNerror) {
/* The UE's location area has changed or,
* the UE's network registration status has changed and
* only location information notification is enabled */
ind.notification.loc.tac = tac;
ind.notification.loc.ci = ci;
ind.notification.loc.AcT = AcT;
rc = user_ind_notify(USER_IND_LOC, (void*)&ind, 0);
}
}
if (rc != RETURNerror) {
LOG_FUNC_RETURN (RETURNok);
}
LOG_FUNC_RETURN (rc);
}
/****************************************************************************
** **
** Name: user_api_esm_callback() **
** **
** Description: Notifies the user application that asynchronous notifica- **
** tion has been received from the EPS Session Management **
** sublayer. **
** **
** Inputs: cid: PDN connection identifier **
** state: PDN connection status **
** Others: None **
** **
** Outputs: None **
** Return: RETURNok, RETURNerror **
** Others: None **
** **
***************************************************************************/
int user_api_esm_callback(int cid, network_pdn_state_t state)
{
LOG_FUNC_IN;
int rc = RETURNok;
user_indication_t ind;
ind.notification.pdn.cid = cid;
ind.notification.pdn.status = state;
/* The status of the specified PDN connection has changed */
rc = user_ind_notify(USER_IND_PDN, (void*)&ind, 0);
LOG_FUNC_RETURN (rc);
}
/****************************************************************************/
/********************* L O C A L F U N C T I O N S *********************/
/****************************************************************************/
/****************************************************************************
** **
** Name: _user_api_send() **
** **
** Description: Encodes and sends data to the user application layer **
** **
** Inputs: data: The data to send **
** Others: _user_api_send_buffer, _user_api_id **
** **
** Outputs: Return: The number of bytes sent when success; **
** RETURNerror Otherwise **
** Others: None **
** **
***************************************************************************/
static int _user_api_send(at_response_t* data)
{
LOG_FUNC_IN;
/* Encode AT command response message */
int bytes = at_response_encode(_user_api_send_buffer, data);
/* Send the AT command response message to the user application */
if (bytes != RETURNerror) {
bytes = _user_api_send_data(bytes);
}
LOG_FUNC_RETURN (bytes);
}
/****************************************************************************
** **
** Name: _user_api_registration_handler() **
** **
** Description: Procedure executed upon receiving registration notifica- **
** tion whenever there is a change in the UE's network re- **
** gistration status in GERAN/UTRAN/E-UTRAN. **
** The network registration data are then displayed to the **
** user. **
** **
** Inputs: id: Network registration AT command identifier **
** data: Generic pointer to the registration data **
** size: Not used **
** Others: None **
** **
** Outputs: None **
** Return: The number of bytes actually sent to the **
** user application **
** Others: None **
** **
***************************************************************************/
static int _user_api_registration_handler(unsigned char id, const void* data,
size_t size)
{
LOG_FUNC_IN;
/* Get registration notification data */
user_ind_reg_t* reg = (user_ind_reg_t*)data;
/* Setup AT command response message for AT+CEREG? read command */
at_response_t at_response;
at_response.id = id; // may be +CREG, +CGREG, +CEREG
at_response.type = AT_COMMAND_GET;
at_response.mask = AT_RESPONSE_NO_PARAM;
at_response.response.cereg.n = AT_CEREG_ON;
at_response.response.cereg.stat = reg->status;
/* Encode and send the AT command response message to the user */
int bytes = _user_api_send(&at_response);
LOG_FUNC_RETURN (bytes);
}
/****************************************************************************
** **
** Name: _user_api_location_handler() **
** **
** Description: Procedure executed upon receiving location notification **
** whenever there is a change in the network serving cell **
** in GERAN/UTRAN/E-UTRAN. **
** The location data are then displayed to the user. **
** **
** Inputs: id: Network registration AT command identifier **
** data: Generic pointer to the registration data **
** size: Not used **
** Others: None **
** **
** Outputs: None **
** Return: The number of bytes actually sent to the **
** user application **
** Others: None **
** **
***************************************************************************/
static int _user_api_location_handler(unsigned char id, const void* data,
size_t size)
{
LOG_FUNC_IN;
/* Get location notification data */
user_ind_loc_t* loc = (user_ind_loc_t*)data;
/* Setup AT command response message for AT+CEREG? read command */
at_response_t at_response;
at_response.id = id; // may be +CREG, +CGREG, +CEREG
at_response.type = AT_COMMAND_GET;
at_response.mask = (AT_CEREG_RESP_TAC_MASK | AT_CEREG_RESP_CI_MASK);
at_response.response.cereg.n = AT_CEREG_BOTH;
at_response.response.cereg.stat = loc->status;
sprintf(at_response.response.cereg.tac, "%.4x", loc->tac); // two byte
sprintf(at_response.response.cereg.ci, "%.8x", loc->ci); // four byte
if (at_response.response.cereg.AcT != NET_ACCESS_UNAVAILABLE) {
at_response.response.cereg.AcT = loc->AcT;
at_response.mask |= AT_CEREG_RESP_ACT_MASK;
}
/* Encode and send the AT command response message to the user */
int bytes = _user_api_send(&at_response);
LOG_FUNC_RETURN (bytes);
}
/****************************************************************************
** **
** Name: _user_api_network_handler() **
** **
** Description: Procedure executed whenever the list of operators present **
** in the network has to be displayed to the user. **
** **
** Inputs: id: Not used **
** data: Generic pointer to the list of operators **
** size: The size of the data to display **
** Others: None **
** **
** Outputs: None **
** Return: The number of bytes actually sent to the **
** user application **
** Others: None **
** **
***************************************************************************/
static int _user_api_network_handler(unsigned char id, const void* data,
size_t size)
{
LOG_FUNC_IN;
/* Setup AT command response message for AT+COPS=? test command */
at_response_t at_response;
at_response.id = AT_COPS;
at_response.type = AT_COMMAND_TST;
at_response.response.cops.tst.data = (char*)data;
at_response.response.cops.tst.size = size;
/* Encode and send the AT command response message to the user */
int bytes = _user_api_send(&at_response);
LOG_FUNC_RETURN (bytes);
}
/****************************************************************************
** **
** Name: _user_api_pdn_connection_handler() **
** **
** Description: Procedure executed upon receiving PDN connection notifi- **
** cation whenever the user or the network has activated or **
** desactivated a PDN connection. **
** The PDN connection data are then displayed to the user. **
** **
** Inputs: id: Not used **
** data: Generic pointer to the PDN connection data **
** size: Not used **
** Others: None **
** **
** Outputs: None **
** Return: The number of bytes actually sent to the **
** user application **
** Others: None **
** **
***************************************************************************/
static int _user_api_pdn_connection_handler(unsigned char id, const void* data,
size_t size)
{
LOG_FUNC_IN;
/* Get PDN connection notification data */
user_ind_pdn_t* pdn = (user_ind_pdn_t*)data;
/* Setup AT command unsolicited result response message for +CGEV */
at_response_t at_response;
at_response.id = AT_CGEV;
at_response.type = AT_COMMAND_GET;
at_response.mask = AT_RESPONSE_CGEV_MASK;
at_response.response.cgev.cid = pdn->cid;
at_response.response.cgev.code = pdn->status;
/* Encode and send the AT command response message to the user */
int bytes = _user_api_send(&at_response);
LOG_FUNC_RETURN (bytes);
}