:::warning
# <center><i class="fa fa-edit"></i> FlexRIC Adding Service Models
</center>
:::
###### tags: `FlexRIC` `xApps` `TEEP` `Internship`
:::success
**Learning Objective:**
The study objectives were to:
- Summarize controller specialization design choices to consider
- Trace FlexRIC code to find out how to add Service Models
:::
[TOC]
## Module 12: Adding Service Models
### 1. Controller Specialization design choices to consider
1. Transport (e.g. encoding scheme: [ASN.1 vs FB](https://hackmd.io/giHZ4QLlSZqhibgY9mhUlA?view=#Comparison-of-ASN1-and-Flatbuffers-E2APE2SM-Encoding))
2. [Service Models](https://hackmd.io/8q6b52TRQuCp-3caB2qS7Q?view=#sm)
3. iApps/Communication interface/xApps
### 2. Adding Service Models
A SM is a protocol which allows the Controller to communicate with a particular RAN functionality, which can be monitored or controlled, e.g.:
1. Handover
2. MAC
3. RLC
4. Etc.
A Service Model acts as an API for controlling a specific RAN function, and can be used by iApps or xApps.
For brief summary on adding SM, see [Summary](#Summary).
### 3. Details
#### 3.1. Add plugin
First, it's necessary to add a plugin. An example of a plugin directory tree is below:
```
sm/mac_sm/
├── CMakeLists.txt
├── dec
│ ├── mac_dec_asn.c
│ ├── mac_dec_asn.h
│ ├── mac_dec_fb.c
│ ├── mac_dec_fb.h
│ ├── mac_dec_generic.h
│ ├── mac_dec_plain.c
│ └── mac_dec_plain.h
├── enc
│ ├── mac_enc_asn.c
│ ├── mac_enc_asn.h
│ ├── mac_enc_fb.c
│ ├── mac_enc_fb.h
│ ├── mac_enc_generic.h
│ ├── mac_enc_plain.c
│ └── mac_enc_plain.h
├── ie
│ ├── e2sm_mac_v00.asn
│ ├── e2sm_mac_v00.fbs
│ ├── fb
│ │ ├── e2sm_mac_stats_v00_builder.h
│ │ ├── e2sm_mac_stats_v00.fbs
│ │ ├── e2sm_mac_stats_v00_reader.h
│ │ ├── e2sm_mac_stats_v00_verifier.h
│ │ ├── flatbuffers_common_builder.h
│ │ └── flatbuffers_common_reader.h
│ ├── mac_data_ie.c
│ └── mac_data_ie.h
├── mac_sm_agent.c
├── mac_sm_agent.h
├── mac_sm_id.h
├── mac_sm_ric.c
├── mac_sm_ric.h
└── test
├── CMakeLists.txt
└── main.c
```
#### 3.2. ID
Each SM requires an id. **Example** (`sm/mac_sm/mac_sm_id.h`):
```c=
const uint16_t SM_MAC_ID = 142;
const char* SM_MAC_STR = "MAC_STATS_V0";
```
#### 3.3. Message Encoding and Decoding
Each SM must implement encoding and decoding of messages. The encoding scheme options are ASN, Flatbuffer or Plain text. **Example** of plain indication message decoding (`sm/mac_sm/dec/mac_dec_plain.c`):
```c=
mac_ind_msg_t mac_dec_ind_msg_plain(size_t len, uint8_t const ind_msg[len])
{
// assert(len == sizeof(mac_ind_msg_t));
mac_ind_msg_t ret;
static_assert(sizeof(uint32_t) == sizeof(ret.len_ue_stats), "Different sizes!");
const size_t len_sizeof = sizeof(ret.len_ue_stats);
memcpy(&ret.len_ue_stats, ind_msg, len_sizeof);
if(ret.len_ue_stats > 0){
ret.ue_stats = calloc(ret.len_ue_stats, sizeof(mac_ue_stats_impl_t));
assert(ret.ue_stats != NULL && "Memory exhausted!");
}
void* ptr = (void*)&ind_msg[len_sizeof];
for(uint32_t i = 0; i < ret.len_ue_stats; ++i){
memcpy(&ret.ue_stats[i], ptr, sizeof( mac_ue_stats_impl_t) );
ptr += sizeof( mac_ue_stats_impl_t);
}
memcpy(&ret.tstamp, ptr, sizeof(ret.tstamp));
ptr += sizeof(ret.tstamp);
assert(ptr == ind_msg + len && "data layout mismacth");
return ret;
}
```
The encoding schemes are dispatched by generics. **Example** (`sm/mac_sm/dec/mac_dec_generic.h`):
```c=
#define mac_dec_ind_msg(T,U,V) _Generic ((T), \
mac_enc_plain_t*: mac_dec_ind_msg_plain , \
mac_enc_asn_t*: mac_dec_ind_msg_asn, \
mac_enc_fb_t*: mac_dec_ind_msg_fb, \
default: mac_dec_ind_msg_plain) (U,V)
```
#### 3.4. SM message handlers
Encoding and decoding procedures are called by SM message handlers. **Example** (`sm/mac_sm/mac_sm_agent.c`):
```c=
static
sm_ind_data_t on_indication_mac_sm_ag(sm_agent_t* sm_agent)
{
//printf("on_indication called \n");
assert(sm_agent != NULL);
sm_mac_agent_t* sm = (sm_mac_agent_t*)sm_agent;
sm_ind_data_t ret = {0};
// Fill Indication Header
mac_ind_hdr_t hdr = {.dummy = 0 };
byte_array_t ba_hdr = mac_enc_ind_hdr(&sm->enc, &hdr );
ret.ind_hdr = ba_hdr.buf;
ret.len_hdr = ba_hdr.len;
// Fill Indication Message
sm_ag_if_rd_t rd_if = {0};
rd_if.type = MAC_STATS_V0;
// This may allocate memory by the RAN
sm->base.io.read(&rd_if);
// Liberate the memory if previously allocated by the RAN. It sucks
// defer({ free_sm_rd_if(&rd_if); }; );
mac_ind_data_t* ind = &rd_if.mac_stats;
defer({ free_mac_ind_hdr(&ind->hdr) ;});
defer({ free_mac_ind_msg(&ind->msg) ;});
defer({ free_mac_call_proc_id(ind->proc_id);});
byte_array_t ba = mac_enc_ind_msg(&sm->enc, &rd_if.mac_stats.msg);
ret.ind_msg = ba.buf;
ret.len_msg = ba.len;
// Fill the optional Call Process ID
ret.call_process_id = NULL;
ret.len_cpid = 0;
return ret;
}
```
Pointers to handlers are tied to the sm structure in the `make_sm()` procedure. **Example** (`sm/mac_sm/mac_sm_agent.c`):
:::warning
**NOTE:** This is the main procedure of a SM plugin. The name of it **must** start with `make_`. A pointer to this function is loaded by `dlopen` -> `dlsym`, by searching for this symbol. This happens e.g. in `ric/plugin_ric.c/` `load_plugin_ric()`.
:::
```c=
sm_agent_t* make_mac_sm_agent(sm_io_ag_t io)
{
sm_mac_agent_t* sm = calloc(1, sizeof(sm_mac_agent_t));
assert(sm != NULL && "Memory exhausted!!!");
sm->base.io = io;
sm->base.free_sm = free_mac_sm_ag;
sm->base.proc.on_subscription = on_subscription_mac_sm_ag;
sm->base.proc.on_indication = on_indication_mac_sm_ag;
sm->base.proc.on_control = on_control_mac_sm_ag;
sm->base.proc.on_ric_service_update = on_ric_service_update_mac_sm_ag;
sm->base.proc.on_e2_setup = on_e2_setup_mac_sm_ag;
sm->base.handle = NULL;
*(uint16_t*)(&sm->base.ran_func_id) = SM_MAC_ID;
assert(strlen( SM_MAC_STR ) < sizeof(sm->base.ran_func_name));
memcpy(sm->base.ran_func_name, SM_MAC_STR, strlen(SM_MAC_STR));
return &sm->base;
}
```
#### 3.5. Modify SM Agent Interface
SM Interface is located under the directory `sm/agent_if`. All source files there should be modified to add a new SM. **Example** (`sm/agent_if/sm_ag_if_wr.h`):
```c=
typedef enum{
SUBSCRIBE_TIMER = 0,
MAC_CTRL_REQ_V0 = 1,
RLC_CTRL_REQ_V0 = 2,
PDCP_CTRL_REQ_V0 = 3,
SLICE_CTRL_REQ_V0 = 4,
// PDCP_OUT_CTRL_V0 = 4,
SM_AGENT_IF_WRITE_V0_END,
} sm_ag_if_wr_e;
typedef struct {
union{
subscribe_timer_t sub_timer;
mac_ctrl_req_data_t mac_ctrl;
rlc_ctrl_req_data_t rlc_ctrl;
pdcp_ctrl_req_data_t pdcp_req_ctrl;
slice_ctrl_req_data_t slice_req_ctrl;
// pdcp_ctrl_out_data_t pdcp_out_ctrl;
};
sm_ag_if_wr_e type;
} sm_ag_if_wr_t;
```
The Agent Interface is used by the Controller for accessing Service Models in a generic way.
#### 3.6. Usage
SM can be used by accessing nearRIC API. Example from `main()` in `test/test_near_ric.c`:
```c=
const uint16_t MAC_ran_func_id = 142;
const char* cmd2 = "Hello";
control_service_near_ric_api(MAC_ran_func_id, cmd2 );
```
#### 3.7. Modify `near_ric.c`
However, in order to add a new SM, `near_ric.c` should be slightly modified. For example, `control_service_near_ric()` currently supports control of only MAC SM:
```c=
void control_service_near_ric(near_ric_t* ric, /*global_e2_node_id_t const* id,*/ uint16_t ran_func_id, const char* cmd)
{
assert(ric != NULL);
assert(ran_func_id > 0);
assert(cmd != NULL);
sm_ric_t* sm = sm_plugin_ric(&ric->plugin ,ran_func_id);
assert(sm->ran_func_id == 142 && "Only ctrl for MAC supported");
sm_ag_if_wr_t wr = {.type = MAC_CTRL_REQ_V0};
wr.mac_ctrl.hdr.dummy = 0;
wr.mac_ctrl.msg.action = 42;
ric_control_request_t ctrl_req = generate_control_request(ric, sm, &wr);
// A pending event is created along with a timer of 1000 ms,
// after which an event will be generated
pending_event_ric_t ev = {.ev = CONTROL_REQUEST_EVENT, .id = ctrl_req.ric_id };
long const wait_ms = 2000;
int fd_timer = create_timer_ms_asio_ric(&ric->io, wait_ms, wait_ms);
//printf("RIC: Control fd_timer for control with value created == %d\n", fd_timer);
int rc = pthread_mutex_lock(&ric->pend_mtx);
assert(rc == 0);
bi_map_insert(&ric->pending, &fd_timer, sizeof(fd_timer), &ev, sizeof(ev));
rc = pthread_mutex_unlock(&ric->pend_mtx);
assert(rc == 0);
byte_array_t ba_msg = e2ap_enc_control_request_ric(&ric->ap, &ctrl_req);
e2ap_send_bytes_ric(&ric->ep, ba_msg);
printf("[NEAR-RIC]: CONTROL SERVICE sent\n");
e2ap_free_control_request_ric(&ric->ap, &ctrl_req);
free_byte_array(ba_msg);
}
```
So it's necessary to modify this procedure to be generic.
### 4. Summary
The steps for adding a SM were described above. To summarize:
1. Add plugin
1.1. SM ID
1.2. SM Messages Encoding and Decoding
1.3. SM Message Handlers
1.4. Main procedure (`make_`) to create a SM `sm_agent_t*` or `sm_ric_t*` structure.
2. Modify SM Agent (`sm/agent_if/*`) to support a new SM
3. Modify `near_ric.c` for supporting generic API to control service
4. Create a shared object (`.so`) out of SM and move it to `/usr/lib/flexric`
5. Use a SM, (e.g. `control_service_near_ric_api(MAC_ran_func_id, cmd2 );`)
### 5. Loading SM in the runtime
During the Agent and RIC initialization, SM from the default directory are loaded. Hovewer, it's also possible to load new SM in the runtime via the following RIC API function:
```c=
void load_sm_near_ric_api(const char* file_path)
{
assert(ric!= NULL);
assert(file_path != NULL);
return load_sm_near_ric(ric, file_path);
}
```
It proceeds to the following function:
```c=
void tx_plugin_ric(plugin_ric_t* p, size_t len, char const file_path[len])
{
assert(p != NULL);
assert(len > 0);
assert(file_exists(file_path) == true);
char const * server_addr = "127.0.0.1";
int const port = 8080;
fd_addr_t fd = init_udp_socket(server_addr, port);
// Send file name
char* ptr = strrchr(file_path, '/');
assert(ptr != NULL && "No absolute path provided");
send_udp_socket(&fd, strlen(ptr +1), ptr + 1);
int const size = file_size(file_path);
// Send file size
send_udp_socket(&fd, sizeof(int), (const char*)&size);
char* data = calloc(1, size+1);
assert(data != NULL && "Memory exhausted");
FILE* fptr=fopen(file_path,"r");
assert(fptr != NULL && "Unable to open file");
int rc = fread(data,size,1,fptr);
assert(rc == 1);
// Send the file itself
send_udp_socket(&fd, size, data);
free(data);
fclose(fptr);
close(fd.sockfd);
}
```
Which transmits SM file name to Agent via UDP port 8080. Then, the following function (which runs in a loop in a separate thread) receives the file name:
```c=
static
void* rx_plugin_agent(void* p_v)
{
plugin_ag_t* p = (plugin_ag_t*)p_v;
const int port = 8080;
char buf[128] = {0};
p->sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
assert(p->sockfd != -1 && "Error creating socket");
struct sockaddr_in serv_addr = {.sin_family = AF_INET,
.sin_port = htons(port),
.sin_addr.s_addr = INADDR_ANY};
int rc = bind(p->sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
assert(rc != -1 && "Error while binding. Address already in use?");
while(true){
struct sockaddr_in cli_addr;
socklen_t len = sizeof(cli_addr);
// Receive file name
rc = recvfrom(p->sockfd, buf, 128, 0, (struct sockaddr *)&cli_addr,&len);
if(p->flag_shutdown)
break;
assert(rc > -1 && rc < 128 && "Buffer overflow");
printf("Name of the file = %s\n",buf);
// Receive file size
int size = 0;
rc = recvfrom(p->sockfd, &size, sizeof(int), 0, (struct sockaddr *)&cli_addr, &len);
if(p->flag_shutdown)
break;
assert(rc == sizeof(int));
printf("Size of the file = %d\n",size);
char* data = calloc(1, size);
assert(data != NULL && "Memory exhausted!");
// Receive file itself
rc = recvfrom(p->sockfd,data,size,0,(struct sockaddr *)&cli_addr, &len);
if(p->flag_shutdown)
break;
assert(rc == size);
// Save file
FILE* fptr = fopen(buf, "wb");
rc = fwrite(data,size,1,fptr);
assert(rc == 1);
free(data);
fclose(fptr);
// Change the file permissions as it is a shared object
int const mode = strtol("0755", 0, 8);
rc = chmod(buf, mode);
assert(rc > -1);
if(p->flag_shutdown)
break;
char full_path[PATH_MAX] = {0};
char* ptr = getcwd(full_path, PATH_MAX);
ptr[strlen(ptr)] = '/';
memcpy(full_path + strlen(full_path), buf, strlen(buf));
// Load the plugin in the agent
load_plugin_ag(p, full_path);
printf("File received and loaded\n");
}
printf("Closing the socket\n");
close(p->sockfd);
return NULL;
}
```
And loads a SM. This, hovewer, works only provided that Agent and RIC are deployed in the same host.