From 9a9c5c97efee2b785dfd816bafd043678292dfb4 Mon Sep 17 00:00:00 2001 From: Abdo-Gaber Date: Mon, 10 Mar 2025 12:49:05 +0100 Subject: [PATCH] Add Data Collection Services (T-Tracer for gNB and UE) - Add _tracer_app_gnb and t_tracer_app_ue apps to common/utils/T/tracer/Makefile - Add Data collection Service for gNB: common/utils/T/tracer/t_tracer_app_gnb.c - Add Data Collection Service for UE: common/utils/T/tracer/t_tracer_app_ue.c - Add Shared memory configuration (common/utils/T/tracer/shared_memory_config.h), since We will have too much memory consumption on the stack if the T-tracer is not fast enough to process the traces or if we have too much traces. As a result, tracers start to capture data, write data in shared memory, then close the T-Tracer after getting the recording of N slots - Support the operation of : COnfig, Record, and Stop - Poll from Socket and not use get_events to mitigate long-time waiting if there is no data in buffer or the UE is already released or crashed - Add extra offset to improve data sync --- common/utils/T/tracer/Makefile | 12 +- common/utils/T/tracer/shared_memory_config.h | 64 ++ common/utils/T/tracer/t_tracer_app_gnb.c | 973 ++++++++++++++++++ common/utils/T/tracer/t_tracer_app_ue.c | 976 +++++++++++++++++++ 4 files changed, 2023 insertions(+), 2 deletions(-) create mode 100644 common/utils/T/tracer/shared_memory_config.h create mode 100644 common/utils/T/tracer/t_tracer_app_gnb.c create mode 100644 common/utils/T/tracer/t_tracer_app_ue.c diff --git a/common/utils/T/tracer/Makefile b/common/utils/T/tracer/Makefile index 807347f5a4..6777be7152 100644 --- a/common/utils/T/tracer/Makefile +++ b/common/utils/T/tracer/Makefile @@ -8,7 +8,7 @@ XLIBS=-lX11 -lpng -lXft all: record replay extract_config textlog enb ue vcd macpdu2wireshark \ extract_input_subframe extract_output_subframe to_vcd extract multi \ - gnb + gnb t_tracer_app_gnb t_tracer_app_ue record: utils.o record.o database.o configuration.o $(CC) $(CFLAGS) -o record $^ $(LIBS) @@ -65,6 +65,14 @@ gnb: utils.o gnb.o database.o event.o handler.o configuration.o \ filter/filter.a $(CC) $(CFLAGS) -o gnb $^ $(LIBS) $(XLIBS) +t_tracer_app_gnb: utils.o t_tracer_app_gnb.o database.o event.o handler.o configuration.o \ + logger/logger.a filter/filter.a + $(CC) $(CFLAGS) -o t_tracer_app_gnb $^ $(LIBS) $(XLIBS) + +t_tracer_app_ue: utils.o t_tracer_app_ue.o database.o event.o handler.o configuration.o \ + logger/logger.a filter/filter.a + $(CC) $(CFLAGS) -o t_tracer_app_ue $^ $(LIBS) $(XLIBS) + multi.o: ../incgen/T_IDs.h ../incgen/T_IDs.h: ../T_messages.txt @@ -90,7 +98,7 @@ filter/filter.a: clean: rm -f *.o core tracer_remote textlog enb ue vcd record replay rm -f extract_config macpdu2wireshark extract_input_subframe - rm -f extract_output_subframe to_vcd extract multi gnb + rm -f extract_output_subframe to_vcd extract multi gnb t_tracer_app_gnb t_tracer_app_ue cd gui && $(MAKE) clean cd view && $(MAKE) clean cd logger && $(MAKE) clean diff --git a/common/utils/T/tracer/shared_memory_config.h b/common/utils/T/tracer/shared_memory_config.h new file mode 100644 index 0000000000..c126d85801 --- /dev/null +++ b/common/utils/T/tracer/shared_memory_config.h @@ -0,0 +1,64 @@ +/* + * 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 common/utils/T/tracer/shared_memory_config.h + * \brief shared memory to store data captured by T-Tracer services to be processed by data recording application + * \author Abdo Gaber + * \date 2024 + * \version 1.0 + * \company Emerson, NI Test and Measurement + * \email: + * \note + * \warning + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef _SHARED_MEMORY_CONFIG_H_ +#define _SHARED_MEMORY_CONFIG_H_ + +#define SHMSIZE ((122.88e6 / (100 * 20)) * 1000 * 8) // Assume capture in 1000, each I+Q represented by 8 byts +// 122.88e6: 10ms at 100 MHz BW of 5G NR +// 20: 20 slots in 10ms +// subcarrier spacing: 30 KHz + +// for gNB T Tracer App +#define GETKEYDIR1_gNB ("/tmp/gnb_app1") +#define GETKEYDIR2_gNB ("/tmp/gnb_app2") +#define PROJECTID_gNB (2335) + +// for UET Tracer App +#define GETKEYDIR1_UE ("/tmp/ue_app1") +#define GETKEYDIR2_UE ("/tmp/ue_app2") +#define PROJECTID_UE (2336) + +void err_exit(char *buf); +int create_shm(char **addrN, const char *pathname, int projectId); +void del_shm(char *addr, int shm_id); + +#endif /* _SHARED_MEMORY_CONFIG_H_ */ diff --git a/common/utils/T/tracer/t_tracer_app_gnb.c b/common/utils/T/tracer/t_tracer_app_gnb.c new file mode 100644 index 0000000000..3cb0325e39 --- /dev/null +++ b/common/utils/T/tracer/t_tracer_app_gnb.c @@ -0,0 +1,973 @@ +/* + * 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 common/utils/T/tracer/t_tracer_app_gnb.c + * \brief T-Tracer gnb service to capture tracee Messages from gNB, it is used by data recording application + * \author Abdo Gaber + * \date 2025 + * \version 1.0 + * \company Emerson, NI Test and Measurement + * \email: + * \note + * \warning + */ + +#include +#include +#include +#include +#include +#include "database.h" +#include "event.h" +#include "handler.h" +#include "logger/logger.h" +#include "utils.h" +#include "event_selector.h" +#include "configuration.h" +#include "shared_memory_config.h" +#include +#include +#include +#include +#include +#include +#include +#define MAX_FRAME_INDEX 1023 +#define MAX_SLOT_INDEX 19 +#define DEBUG_T_Tracer +#define DEBUG_BUFFER +// Duration to discard recording in milliseconds to mitigate reading data from +// the buffer in the stack from the previous record +#define DISCARD_RECORD_DURATION_MS 10 + +// Combine bytes in - little-endian format +int combine_bytes(const uint8_t *bytes, size_t num_bytes) +{ + int result = 0; + for (size_t i = 0; i < num_bytes; ++i) { + result |= (bytes[i] << (i * 8)); + } + return result; +} + +// Convert an integer to an array of bytes - little-endian format +void int_to_bytes(int num, uint8_t *bytes, size_t num_bytes) +{ + for (size_t i = 0; i < num_bytes; ++i) { + bytes[i] = (num >> (i * 8)) & 0xFF; + } +} + +// Check if the message is in the list of bits messages +bool is_bits_messages(int traces_bits_support_data_Collection_format_idx[], int n_bits_msgs, int msg_id) +{ + for (int i = 0; i < n_bits_msgs; i++) { + if (msg_id == traces_bits_support_data_Collection_format_idx[i]) { + return true; + } + } + return false; +} + +// Get the current time +struct timespec get_current_time() +{ + struct timespec time; + clock_gettime(CLOCK_MONOTONIC, &time); + return time; +} + +// Function to convert timespec to microseconds +long long timespec_to_microseconds(struct timespec time) +{ + return (time.tv_sec * 1000000LL) + (time.tv_nsec / 1000); +} + +// Calculate the time difference in milliseconds +double calculate_time_difference(struct timespec start, struct timespec end) +{ + double start_ms = start.tv_sec * 1000.0 + start.tv_nsec / 1000000.0; + double end_ms = end.tv_sec * 1000.0 + end.tv_nsec / 1000000.0; + return end_ms - start_ms; +} + +// Get Time Stamp in microseconds in YYYYMMDDHHMMSSmmmuuu format +char *get_time_stamp_usec(char time_stamp_str[]) +{ + // initialization to measure time stamp --This part should be moved to inilization part + time_t my_time; + struct tm *timeinfo; + time(&my_time); + struct timeval tv; + + // get time stamp + timeinfo = localtime(&my_time); + gettimeofday(&tv, NULL); + // Add time stamp: yyyy mm dd hh mm ss usec + uint16_t year = timeinfo->tm_year + 1900; + uint8_t mon = timeinfo->tm_mon + 1; + uint8_t mday = timeinfo->tm_mday; + uint8_t hour = timeinfo->tm_hour; + uint8_t min = timeinfo->tm_min; + uint8_t sec = timeinfo->tm_sec; + uint16_t usec = (tv.tv_usec); + // printf ("Time stamp: %d_%d_%d_%d_%d_%d_%d \n",year,mon,mday,hour,min,sec,usec); + // sprintf(time_stamp_str, "%d_%d_%d_%d_%d_%d_%d",year,mon,mday,hour,min,sec,usec); + sprintf(time_stamp_str, "%04d%02d%02d%02d%02d%02d%06d", year, mon, mday, hour, min, sec, usec); + + return time_stamp_str; +} + +// Convert timestamp string to integer +int convert_time_stamp_to_int(const char *timestamp) +{ + return atoi(timestamp); +} + +// Split timestamp string and convert to integer +int split_time_stamp_and_convert_to_int(char time_stamp_str[], int shift, int length) +{ + char time_part[length + 1]; // Buffer to hold the date part YYYYMMDD or HHMMSSmmm + // Copy the first 8 or 9 characters (YYYYMMDD) to HHMMSSmmm + strncpy(time_part, time_stamp_str + shift, length); + time_part[length] = '\0'; // Null-terminate the string + // Convert timestamp string to integer + return convert_time_stamp_to_int(time_part); +} + +void err_exit(char *buf) +{ + fprintf(stderr, "%s\n", buf); + exit(1); +} + +// create shared memory +int create_shm(char **addrN, const char *shm_path, int projectId) +{ + key_t key; + if (-1 != open(shm_path, O_CREAT, 0777)) { + key = ftok(shm_path, projectId); + } else { + err_exit("Error: open shared memory"); + } + + if (key < 0) { + err_exit("Error: ftok error"); + } + + int shm_id; + shm_id = shmget(key, SHMSIZE, IPC_CREAT | IPC_EXCL | 0664); + if (shm_id == -1) { + if (errno == EEXIST) { + printf("Error: shared memeory already exist\n"); + shm_id = shmget(key, 0, 0); + printf("reference shm_id = %d\n", shm_id); + } else { + perror("errno"); + err_exit("shmget error"); + } + } + char *addr; + // address to attach - attach for read & write + if ((addr = shmat(shm_id, 0, 0)) == (void *)-1) { + if (shmctl(shm_id, IPC_RMID, NULL) == -1) + err_exit("Error: shmctl error"); + else { + printf("Attach shared memory failed\n"); + printf("remove shared memory identifier successful\n"); + } + err_exit("shmat error"); + } + *addrN = addr; + return shm_id; +} + +// delete shared memory +void del_shm(char *addr, int shm_id) +{ + if (shmdt(addr) < 0) + err_exit("shmdt error"); + + if (shmctl(shm_id, IPC_RMID, NULL) == -1) + err_exit("shmctl error"); + else { + printf("Remove shared memory identifier successful\n"); + } +} + +/* this function sends the activated traces to the nr-softmodem */ +void activate_traces(int socket, int number_of_events, int *is_on) +{ + char t = 1; + if (socket_send(socket, &t, 1) == -1 || socket_send(socket, &number_of_events, sizeof(int)) == -1 + || socket_send(socket, is_on, number_of_events * sizeof(int)) == -1) + abort(); +} + +void usage(void) +{ + printf( + "options:\n" + " -d this option is mandatory\n" + " -ip connect to given IP address (default %s)\n" + " -p connect to given port (default %d)\n", + DEFAULT_REMOTE_IP, + DEFAULT_REMOTE_PORT); + exit(1); +} + +// class to store the message and the action to enable capture selected data +// 0: do not record message +// 1: record message +struct trace_struct { + char on_off_name[100]; + int on_off_action; +}; + +// struct for trace message based on Data Collection Trace Messages Structure +// you need to define the vararibles of each message +typedef struct { + /* Data Collection Trace Message Structure */ + int frame; + int slot; + int datetime_yyyymmdd; + int datetime_hhmmssmmm; + int frame_type, freq_range, subcarrier_spacing, cyclic_prefix, symbols_per_slot; + int Nid_cell, rnti; + int rb_size, rb_start, start_symbol_index, nr_of_symbols; + int qam_mod_order, mcs_index, mcs_table, nrOfLayers, transform_precoding; + int dmrs_config_type, ul_dmrs_symb_pos, number_dmrs_symbols; + int dmrs_port, dmrs_nscid, nb_antennas_rx, number_of_bits; + int data_size, data; +} event_trace_msg_data; + +void setup_trace_msg_data(event_trace_msg_data *d, void *database) +{ + database_event_format f; + int i; + + /* Data Collection Trace Message Structure */ + // FORMAT = int,frame : int,slot : int,datetime_yyyymmdd : int,datetime_hhmmssmmm : + // int,frame_type : int,freq_range : int,subcarrier_spacing : int,cyclic_prefix : int,symbols_per_slot : + // int,Nid_cell : int,rnti : + // int,rb_size : int,rb_start : int,start_symbol_index : int,nr_of_symbols : + // int,qam_mod_order : int,mcs_index : int,mcs_table : int,nrOfLayers : + // int,transform_precoding : int,dmrs_config_type : int,ul_dmrs_symb_pos : int,number_dmrs_symbols : + // int,dmrs_port : int,dmrs_nscid : + // int,nb_antennas_rx : int,number_of_bits : buffer,data + + // Initialize the data structure + d->frame = -1; + d->slot = -1; + d->datetime_yyyymmdd = -1; + d->datetime_hhmmssmmm = -1; + d->frame_type = -1; + d->freq_range = -1; + d->subcarrier_spacing = -1; + d->cyclic_prefix = -1; + d->symbols_per_slot = -1; + d->Nid_cell = -1; + d->rnti = -1; + d->rb_size = -1; + d->rb_start = -1; + d->start_symbol_index = -1; + d->nr_of_symbols = -1; + d->qam_mod_order = -1; + d->mcs_index = -1; + d->mcs_table = -1; + d->nrOfLayers = -1; + d->transform_precoding = -1; + d->dmrs_config_type = -1; + d->ul_dmrs_symb_pos = -1; + d->number_dmrs_symbols = -1; + d->dmrs_port = -1; + d->dmrs_nscid = -1; + d->nb_antennas_rx = -1; + d->number_of_bits = -1; + d->data = -1; + +/* this macro looks for a particular element and checks its type */ +#define G(var_name, var_type, var) \ + if (!strcmp(f.name[i], var_name)) { \ + if (strcmp(f.type[i], var_type)) { \ + printf("Error: Trace Message has a bad type for %s\n", var_name); \ + exit(1); \ + } \ + var = i; \ + continue; \ + } + + /* ------------------------------*/ + /* Create Macro for Data Collection Trace Message */ + /* ------------------------------*/ + // Data Collection Trace Message Structure + // Example: GNB_PHY_UL_FD_PUSCH_IQ, GNB_PHY_UL_FD_DMRS_ID, + // GNB_PHY_UL_FD_CHAN_EST_DMRS_POS, GNB_PHY_UL_FD_CHAN_EST_DMRS_INTERPL + // GNB_PHY_UL_PAYLOAD_RX_BITS + // UE_PHY_UL_SCRAMBLED_TX_BITS + // UE_PHY_UL_PAYLOAD_TX_BITS + // Get a template of any message based on Data Collection Trace Messages Structure + int Trace_MSG_ID = event_id_from_name(database, "GNB_PHY_UL_FD_PUSCH_IQ"); + f = get_format(database, Trace_MSG_ID); + + /* get the elements of the trace + * the value is an index in the event, see below */ + // FORMAT = int,frame : int,slot : int,datetime_yyyymmdd : int,datetime_hhmmssmmm : + // int,frame_type : int,freq_range : int,subcarrier_spacing : int,cyclic_prefix : int,symbols_per_slot : + // int,Nid_cell : int,rnti : + // int,rb_size : int,rb_start : int,start_symbol_index : int,nr_of_symbols : + // int,qam_mod_order : int,mcs_index : int,mcs_table : int,nrOfLayers : + // int,transform_precoding : int,dmrs_config_type : int,ul_dmrs_symb_pos : int,number_dmrs_symbols : + // int,dmrs_port : int,dmrs_nscid : + // int,nb_antennas_rx : int,number_of_bits : buffer,data + + for (i = 0; i < f.count; i++) { + G("frame", "int", d->frame) + G("slot", "int", d->slot) + G("datetime_yyyymmdd", "int", d->datetime_yyyymmdd) + G("datetime_hhmmssmmm", "int", d->datetime_hhmmssmmm) + G("frame_type", "int", d->frame_type) + G("freq_range", "int", d->freq_range) + G("subcarrier_spacing", "int", d->subcarrier_spacing) + G("cyclic_prefix", "int", d->cyclic_prefix) + G("symbols_per_slot", "int", d->symbols_per_slot) + G("Nid_cell", "int", d->Nid_cell) + G("rnti", "int", d->rnti) + G("rb_size", "int", d->rb_size) + G("rb_start", "int", d->rb_start) + G("start_symbol_index", "int", d->start_symbol_index) + G("nr_of_symbols", "int", d->nr_of_symbols) + G("qam_mod_order", "int", d->qam_mod_order) + G("mcs_index", "int", d->mcs_index) + G("mcs_table", "int", d->mcs_table) + G("nrOfLayers", "int", d->nrOfLayers) + G("transform_precoding", "int", d->transform_precoding) + G("dmrs_config_type", "int", d->dmrs_config_type) + G("ul_dmrs_symb_pos", "int", d->ul_dmrs_symb_pos) + G("number_dmrs_symbols", "int", d->number_dmrs_symbols) + G("dmrs_port", "int", d->dmrs_port) + G("dmrs_nscid", "int", d->dmrs_nscid) + G("nb_antennas_rx", "int", d->nb_antennas_rx) + G("number_of_bits", "int", d->number_of_bits) + G("data", "buffer", d->data) + } + // if (d->frame == -1 || d->slot == -1) goto error; +#undef G + return; +} + +// Function to check if a value is in the array +int isValueInArray(int value, int arr[], int size) +{ + for (int i = 0; i < size; i++) { + if (arr[i] == value) { + return 1; // Value found + } + } + return 0; // Value not found +} + +void reestablish_connection(int *socket, char *ip, int port, int number_of_events, int *is_on) +{ + clear_remote_config(); + if (*socket != -1) + close(*socket); + + /* connect to the nr-softmodem */ + *socket = connect_to(ip, port); + if (*socket == -1) { + printf("\n Failed to connect to nr-softmodem. Retrying...\n"); + return; + } + printf("\n Connected"); + + /* activate the traces in the nr-softmodem */ + activate_traces(*socket, number_of_events, is_on); + printf("\n Activated Traces in nr-softmodem"); +} + +int main(int n, char **v) +{ + // Define and initialize an array of strings to list + // trace_msgs_support_data_Collection_format + // it is used to parse the requested messages if it is based + // on Data Collection Trace Messages Structure and supported tracer messages indices + char *traces_iq_support_data_Collection_format[] = {"GNB_PHY_UL_FD_PUSCH_IQ", + "GNB_PHY_UL_FD_DMRS", + "GNB_PHY_UL_FD_CHAN_EST_DMRS_POS", + "GNB_PHY_UL_FD_CHAN_EST_DMRS_INTERPL"}; + char *traces_bits_support_data_Collection_format[] = {"GNB_PHY_UL_PAYLOAD_RX_BITS", + "UE_PHY_UL_SCRAMBLED_TX_BITS", + "UE_PHY_UL_PAYLOAD_TX_BITS"}; + // extra number of records to simlify Sync between base station and UE synchronization records. + // if you have network delay, you can increase the number of records to capture + int max_sync_offset = 6; // 6 frames ~ 60 ms + // all supported messages + // Calculate the size of the combined array + int n_iq_msgs = sizeof(traces_iq_support_data_Collection_format) / sizeof(traces_iq_support_data_Collection_format[0]); + int n_bits_msgs = sizeof(traces_bits_support_data_Collection_format) / sizeof(traces_bits_support_data_Collection_format[0]); + int n_msgs_based_data_Collection_format = n_iq_msgs + n_bits_msgs; + + // Create the combined array + char *traces_support_data_Collection_format[n_msgs_based_data_Collection_format]; + // Copy IQ messages to the combined array + for (int i = 0; i < n_iq_msgs; i++) { + traces_support_data_Collection_format[i] = traces_iq_support_data_Collection_format[i]; + } + + // Copy Bits messages to the combined array + for (int i = 0; i < n_bits_msgs; i++) { + traces_support_data_Collection_format[i + n_iq_msgs] = traces_bits_support_data_Collection_format[i]; + } + + uint16_t msg_id = 0; + uint16_t start_frame_number = 0; + uint32_t number_records = 0; // number of records to capture, it is number of slots + // array to store the requested tracer messages indices + int req_tracer_msgs_indices[100] = {0}; + // define variables --> to do: add all of them to be class of pointers + char *database_filename = NULL; + void *database; + char *ip = DEFAULT_REMOTE_IP; + char ip_address[16]; // max IP address length is 15 + 1 for null terminator + int port = DEFAULT_REMOTE_PORT; + int *is_on; + int number_of_events; + int i; + int socket = -1; + char trace_time_stamp_str[30]; + + // data structure for the trace messages based on Data Collection Trace Messages Structure + event_trace_msg_data trace_msg_data; + + // initlization variables + unsigned int bufIdx_wr = 0; + unsigned int bufIdx_rd = 0; + uint8_t num_req_tracer_msgs = 0; + + // initilaze shared memory + char *addr_wr, *addr_rd; + printf("\n Data Collection Service: Initializing shared memory ..."); + printf("\n Directory 1: %s, Directory 2: %s", GETKEYDIR1_gNB, GETKEYDIR2_gNB); + printf("\n Project ID: %d\n", PROJECTID_gNB); + int shm_id_wr = create_shm(&addr_wr, GETKEYDIR1_gNB, PROJECTID_gNB); + int shm_id_rd = create_shm(&addr_rd, GETKEYDIR2_gNB, PROJECTID_gNB); + del_shm(addr_wr, shm_id_wr); + del_shm(addr_rd, shm_id_rd); + shm_id_wr = create_shm(&addr_wr, GETKEYDIR1_gNB, PROJECTID_gNB); + shm_id_rd = create_shm(&addr_rd, GETKEYDIR2_gNB, PROJECTID_gNB); + + /* write on a socket fails if the other end is closed and we get SIGPIPE */ + if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) + abort(); + + /* parse command line options */ + // Port number and IP address are given via API and not via command line + for (i = 1; i < n; i++) { + if (!strcmp(v[i], "-h") || !strcmp(v[i], "--help")) + usage(); + if (!strcmp(v[i], "-d")) { + if (i > n - 2) + usage(); + database_filename = v[++i]; + continue; + } + if (!strcmp(v[i], "-ip")) { + if (i > n - 2) + usage(); + ip = v[++i]; + continue; + } + if (!strcmp(v[i], "-p")) { + if (i > n - 2) + usage(); + port = atoi(v[++i]); + continue; + } + usage(); + } + + if (database_filename == NULL) { + printf("ERROR: Provide a database file (-d)\n"); + exit(1); + } + + /* load the database T_messages.txt */ + database = parse_database(database_filename); + load_config_file(database_filename); + + /* an array of int for all the events defined in the database is needed */ + number_of_events = number_of_ids(database); + is_on = calloc(number_of_events, sizeof(int)); + if (is_on == NULL) + abort(); + + // Set first byte of the shared memory to 0 + addr_rd[0] = 0; // important to check if we have new requested messages + + // read requested tracer msg indices from memory + printf("\n Data Collection Service: Waiting for messages request ...\n"); + // 0: Wait + // 1: config + // 2: record + // 3: stop + // Wait for Action Config + while (1) { + if ((uint8_t)(addr_rd[0]) == 0) { + usleep(50); // sleep for 50 us: 0.5 ms slot duration + } + // config state + else if ((uint8_t)(addr_rd[0]) == 1) { + get_time_stamp_usec(trace_time_stamp_str); + printf("\n Received config message. Time Stamp: %s", trace_time_stamp_str); + + // get the IP address length in bytes + bufIdx_rd = 1; + uint8_t ip_address_length = addr_rd[bufIdx_rd]; + bufIdx_rd += 1; // + 1 byte = IP address length + + // read the IP address + for (int i = 0; i < ip_address_length; i++) { + ip_address[i] = addr_rd[bufIdx_rd]; + bufIdx_rd += 1; + } + + ip = ip_address; + + // get the array bytes of the port number : 2 bytes + uint8_t port_number_bytes[2] = {addr_rd[bufIdx_rd], addr_rd[bufIdx_rd + 1]}; + bufIdx_rd += 2; // + 2 bytes = start frame number + + port = combine_bytes(port_number_bytes, 2); + printf("\n Parameters: IP Address length: %d, IP Address: %s, Port Number: %d \n", ip_address_length, ip_address, port); + addr_rd[0] = 0; // reset memory : to wait for record action + break; + } + } + // Establish connection to the nr-softmodem + clear_remote_config(); + if (socket != -1) + close(socket); + + /* connect to the nr-softmodem */ + socket = connect_to(ip, port); + printf("\n Connected to nr-softmodem"); + + // Read Action record or stop + printf("\n Data Collection Service: Waiting for record message ...\n"); + while (1) { + // wait for Action record + if ((uint8_t)(addr_rd[0]) == 0) { + usleep(50); // sleep for 50 us: 0.5 ms slot duration + } + + // quit state + else if ((uint8_t)(addr_rd[0]) == 3) { + printf("\n Received 'stop' command. Exiting..."); + printf("\n "); + // Clean up and exit + break; + } + // record state + else if ((uint8_t)(addr_rd[0]) == 2) { + // clear remote buffer if there is + clear_remote_config(); + get_time_stamp_usec(trace_time_stamp_str); + printf("\n Received record message. Time Stamp: %s", trace_time_stamp_str); + + // read number of requested messages + bufIdx_rd = 1; + num_req_tracer_msgs = addr_rd[bufIdx_rd]; + bufIdx_rd += 1; + printf("\n Number of requested tracer messages: %d,", num_req_tracer_msgs); + // reset memory : action to wait for next record action + addr_rd[0] = 0; + + // read tracer msg IDs - every message ID has been stored in two bytes + for (uint8_t msg_n = 0; msg_n < num_req_tracer_msgs; msg_n++) { + // get the array bytes of the tracer message ID: 2 bytes + uint8_t msg_id_bytes[2] = {addr_rd[bufIdx_rd], addr_rd[bufIdx_rd + 1]}; + bufIdx_rd += 2; // + 2 bytes = message ID + msg_id = combine_bytes(msg_id_bytes, 2); + + req_tracer_msgs_indices[msg_n] = msg_id; + printf(" msg_id: %d, ", msg_id); + } + // get the array bytes of the number of records: 4 bytes + uint8_t number_records_bytes[4] = {addr_rd[bufIdx_rd], + addr_rd[bufIdx_rd + 1], + addr_rd[bufIdx_rd + 2], + addr_rd[bufIdx_rd + 3]}; + bufIdx_rd += 4; // + 4 bytes = number of records + + number_records = combine_bytes(number_records_bytes, 4); + + printf("num_records: %d, ", number_records); + + // get the array bytes of the start frame number : 2 bytes + uint8_t start_frame_number_bytes[2] = {addr_rd[bufIdx_rd], addr_rd[bufIdx_rd + 1]}; + bufIdx_rd += 2; // + 2 bytes = start frame number + + start_frame_number = combine_bytes(start_frame_number_bytes, 2); + + printf("start_frame: %d\n", start_frame_number); + + /* activate the trace in this array */ + printf("\n Activate Tracer messages in on_off array: "); + for (i = 0; i < num_req_tracer_msgs; i++) { + char *on_off_name = event_name_from_id(database, req_tracer_msgs_indices[i]); + on_off(database, on_off_name, is_on, 1); + printf("%d, %s, ", req_tracer_msgs_indices[i], on_off_name); + // on_off(database, "GNB_PHY_DL_OUTPUT_SIGNAL", is_on, 1); + } + + // get the event IDs for the bit messages + int traces_bits_support_data_Collection_format_idx[n_bits_msgs]; + for (int i = 0; i < n_bits_msgs; i++) { + traces_bits_support_data_Collection_format_idx[i] = + event_id_from_name(database, traces_bits_support_data_Collection_format[i]); + } + + // all supported messages + int traces_support_data_Collection_format_idx[n_msgs_based_data_Collection_format]; + for (int i = 0; i < n_msgs_based_data_Collection_format; i++) { + traces_support_data_Collection_format_idx[i] = event_id_from_name(database, traces_support_data_Collection_format[i]); + } + + // setup data for the trace messages + setup_trace_msg_data(&trace_msg_data, database); + printf("\n Setup Trace Data Done"); + + // Get the start time + struct timespec start_time = get_current_time(); + + /* activate the tracee in the nr-softmodem */ + activate_traces(socket, number_of_events, is_on); + printf("\n Activated Traces in nr-softmodem"); + + /* a buffer needed to receive events from the nr-softmodem */ + OBUF ebuf = {osize : 0, omaxsize : 0, obuf : NULL}; + + /* read events */ + int nrecord_idx = 0; + bufIdx_wr = 0; + // logic to be sure that we have a complete slot record + bool start_recording = false; + bool got_ref_frame_slot = false; + int ref_slot = 0; + int ref_frame = 0; + int slot_difference = 0; + int frame_difference = 0; + int current_frame = 0, prev_frame = 0, current_slot = 0, prev_slot = 0; + // offset to sync between base station and UE synchronization records or power measurements + int sync_offset_index = 0; // increase the index only if the index of frame changes after getting all records + // since we will use the frame differece to do extra records, we should be sure that the last slot is recorded completely + printf("\n\n Data Collection Service: Start reading messages ..."); + struct pollfd event_poll_fd; + event_poll_fd.fd = socket; + event_poll_fd.events = POLLIN; + while (1) { + // stop if number of records is done + if ((nrecord_idx >= number_records) && (sync_offset_index >= max_sync_offset)) { + // We added one to the number of records to capture the last record completely if + // we have several messages enabled per slot + break; + } + int poll_ret = poll(&event_poll_fd, 1, 1); // 1 ms timeout for poll + if (poll_ret > 0 && (event_poll_fd.revents & POLLIN)) { + event e = get_event(socket, &ebuf, database); + + if (e.type == -1) { + printf("\n Link broken or unexpected message received. Re-establishing connection...\n"); + reestablish_connection(&socket, ip, port, number_of_events, is_on); + continue; // Skip further processing and retry + } + //------------------------- + // GNB_PHY_UL_FD_PUSCH_IQ, GNB_PHY_UL_FD_DMRS_ID, GNB_PHY_UL_FD_CHAN_EST_DMRS_POS, + // UE_PHY_UL_SCRAMBLED_TX_BITS, GNB_PHY_UL_PAYLOAD_RX_BITS, UE_PHY_UL_PAYLOAD_TX_BITS + //------------------------- + // is it a requested message + if (isValueInArray(e.type, req_tracer_msgs_indices, num_req_tracer_msgs)) { + // is it based on Data Collection Trace Messages Structure + if (isValueInArray(e.type, traces_support_data_Collection_format_idx, n_msgs_based_data_Collection_format)) { + // Start recording from the next slot to mitigate capturing partial data + // check if the current frame and slot are different from the previous frame and slot + // Then, increase the record index + if (start_recording == false) { + if (got_ref_frame_slot == false) { + ref_frame = e.e[trace_msg_data.frame].i; + ref_slot = e.e[trace_msg_data.slot].i; + printf("\nMessage Info: msg_id %s (%d) \n", event_name_from_id(database, e.type), e.type); + got_ref_frame_slot = true; + } + + current_frame = e.e[trace_msg_data.frame].i; + current_slot = e.e[trace_msg_data.slot].i; + frame_difference = (current_frame - ref_frame + MAX_FRAME_INDEX + 1) % (MAX_FRAME_INDEX + 1); + slot_difference = (current_slot - ref_slot + MAX_SLOT_INDEX + 1) % (MAX_SLOT_INDEX + 1); + printf("\n First frame.slot: %d.%d, current frame.slot: %d.%d, diff frame.slot: %d.%d", + ref_frame, + ref_slot, + current_frame, + current_slot, + frame_difference, + slot_difference); + + if ((ref_frame != current_frame) || (ref_slot != current_slot)) { + start_recording = true; + printf("\n Start recording from frame: %d, slot: %d ", e.e[trace_msg_data.frame].i, e.e[trace_msg_data.slot].i); + } + } + // start recording from the next frame to mitigate capturing partial data + if (start_recording == true) { + /* this is how to access the elements of the Data Collection trace messages. + * we use e.e[] and then the correct suffix, here + * it's .i for the integer and .b for the buffer and .bsize for the buffer size + * see in event.h the structure event_arg + */ + unsigned char *buf = e.e[trace_msg_data.data].b; + + printf("\n\nRecord number: %d", nrecord_idx); +#ifdef DEBUG_BUFFER + printf("\nBuffer index in bytes: %d", bufIdx_wr); +#endif + + // add general message header: message ID, + // T-Tracer Message format + // FORMAT = int,frame : int,slot : int,datetime_yyyymmdd : int,datetime_hhmmssmmm : + // int,frame_type : int,freq_range : int,subcarrier_spacing : int,cyclic_prefix : int,symbols_per_slot : + // int,Nid_cell : int,rnti : + // int,rb_size : int,rb_start : int,start_symbol_index : int,nr_of_symbols : + // int,qam_mod_order : int,mcs_index : int,mcs_table : int,nrOfLayers : + // int,transform_precoding : int,dmrs_config_type : int,ul_dmrs_symb_pos : int,number_dmrs_symbols : + // int,dmrs_port : int,dmrs_nscid : + // int,nb_antennas_rx : int,number_of_bits : buffer,data + memcpy(&addr_wr[bufIdx_wr], &e.type, sizeof(uint16_t)); + bufIdx_wr += sizeof(uint16_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.frame].i, sizeof(uint16_t)); + bufIdx_wr += sizeof(uint16_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.slot].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.datetime_yyyymmdd].i, sizeof(uint32_t)); + bufIdx_wr += sizeof(uint32_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.datetime_hhmmssmmm].i, sizeof(uint32_t)); + bufIdx_wr += sizeof(uint32_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.frame_type].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.freq_range].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.subcarrier_spacing].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.cyclic_prefix].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.symbols_per_slot].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.Nid_cell].i, sizeof(uint16_t)); + bufIdx_wr += sizeof(uint16_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.rnti].i, sizeof(uint16_t)); + bufIdx_wr += sizeof(uint16_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.rb_size].i, sizeof(uint16_t)); + bufIdx_wr += sizeof(uint16_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.rb_start].i, sizeof(uint16_t)); + bufIdx_wr += sizeof(uint16_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.start_symbol_index].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.nr_of_symbols].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.qam_mod_order].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.mcs_index].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.mcs_table].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.nrOfLayers].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.transform_precoding].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.dmrs_config_type].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.ul_dmrs_symb_pos].i, sizeof(uint16_t)); + bufIdx_wr += sizeof(uint16_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.number_dmrs_symbols].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.dmrs_port].i, sizeof(uint16_t)); + bufIdx_wr += sizeof(uint16_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.dmrs_nscid].i, sizeof(uint16_t)); + bufIdx_wr += sizeof(uint16_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.nb_antennas_rx].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.number_of_bits].i, sizeof(uint32_t)); + bufIdx_wr += sizeof(uint32_t); + + printf("\nTime Stamp: %d_%d", e.e[trace_msg_data.datetime_yyyymmdd].i, e.e[trace_msg_data.datetime_hhmmssmmm].i); + + // add message body: length in bytes + recorded data + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.data].bsize, sizeof(uint32_t)); + bufIdx_wr += sizeof(uint32_t); + + // read data from buffer and convert from unsigned char * array to int 16 using the right endianness + // T-tracer: Little Endian + // For BITS Messages, example: UE_PHY_UL_SCRAMBLED_TX_BITS: data in bytes + + if (is_bits_messages(traces_bits_support_data_Collection_format_idx, n_bits_msgs, e.type)) { + for (int byte_idx = 0; byte_idx < e.e[trace_msg_data.data].bsize; byte_idx += 1) { + // printf("%d, ", buf[byte_idx]); + memcpy(&addr_wr[bufIdx_wr], &buf[byte_idx], sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + } + + } else { + for (int byte_idx = 0; byte_idx < e.e[trace_msg_data.data].bsize; byte_idx += 2) { + // For a little-endian system: + memcpy(&addr_wr[bufIdx_wr], &buf[byte_idx], sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &buf[byte_idx + 1], sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + } + } + /* + for (int byte_idx_i = 0; byte_idx_i < e.e[d.nr_ul_fd_dmrs_data].bsize; byte_idx_i+=4) { + int16_t I = buf[byte_idx_i] | (buf[byte_idx_i+1] << 8); + int16_t Q = buf[byte_idx_i+2] | (buf[byte_idx_i+3] << 8); + printf ("idx %d, ", byte_idx_i); + printf ("\n%d", I); + printf ("\n%d", Q); + } + */ + // check if the current frame and slot are different from the previous frame and slot + // Then, increase the record index + current_frame = e.e[trace_msg_data.frame].i; + current_slot = e.e[trace_msg_data.slot].i; + + // increase sync offset index if the current frame is different from the previous frame + if ((current_frame != prev_frame) && nrecord_idx >= number_records) { + sync_offset_index++; + } + + if (current_frame != prev_frame || current_slot != prev_slot) { + nrecord_idx++; + // Update previous frame and slot numbers + prev_frame = current_frame; + prev_slot = current_slot; + } +#ifdef DEBUG_T_Tracer + printf("\nMessage Info: msg_id %s (%d) \n", event_name_from_id(database, e.type), e.type); + printf("frame %d, slot %d, datetime %d_%d\n", + e.e[trace_msg_data.frame].i, + e.e[trace_msg_data.slot].i, + e.e[trace_msg_data.datetime_yyyymmdd].i, + e.e[trace_msg_data.datetime_hhmmssmmm].i); + printf("frame_type %d, freq_range %d, subcarrier_spacing %d, cyclic_prefix %d, symbols_per_slot %d\n", + e.e[trace_msg_data.frame_type].i, + e.e[trace_msg_data.freq_range].i, + e.e[trace_msg_data.subcarrier_spacing].i, + e.e[trace_msg_data.cyclic_prefix].i, + e.e[trace_msg_data.symbols_per_slot].i); + printf("Nid_cell %d, rnti %d, rb_size %d, rb_start %d, start_symbol_index %d, nr_of_symbols %d\n", + e.e[trace_msg_data.Nid_cell].i, + e.e[trace_msg_data.rnti].i, + e.e[trace_msg_data.rb_size].i, + e.e[trace_msg_data.rb_start].i, + e.e[trace_msg_data.start_symbol_index].i, + e.e[trace_msg_data.nr_of_symbols].i); + printf("qam_mod_order %d, mcs_index %d, mcs_table %d, nrOfLayers %d, transform_precoding %d\n", + e.e[trace_msg_data.qam_mod_order].i, + e.e[trace_msg_data.mcs_index].i, + e.e[trace_msg_data.mcs_table].i, + e.e[trace_msg_data.nrOfLayers].i, + e.e[trace_msg_data.transform_precoding].i); + printf("dmrs_config_type %d, ul_dmrs_symb_pos %d, number_dmrs_symbols %d, dmrs_port %d, dmrs_nscid %d\n", + e.e[trace_msg_data.dmrs_config_type].i, + e.e[trace_msg_data.ul_dmrs_symb_pos].i, + e.e[trace_msg_data.number_dmrs_symbols].i, + e.e[trace_msg_data.dmrs_port].i, + e.e[trace_msg_data.dmrs_nscid].i); + printf("nb_antennas_rx %d, number_of_bits %d, data size %d\n", + e.e[trace_msg_data.nb_antennas_rx].i, + e.e[trace_msg_data.number_of_bits].i, + e.e[trace_msg_data.data].bsize); +#endif + } // End of start recording flag + } // end of if statement for the supported messages based on Data Collection Trace Messages Structure + else { + printf("ERROR: Requested Message is not based on Data Collection Trace Messages Structure\n"); + printf("ERROR: Requested Message ID: %d\n", e.type); + } + } // end of if statement for the requested messages + } // end while loop of reading events + else { + // No data, just loop and check time + usleep(100); // optional: avoid busy-waiting + } + } // End of while loop to read events + + // de-activate the tracee in the nr-softmodem + printf("\n De-activated Tracer message:\n"); + for (i = 0; i < num_req_tracer_msgs; i++) { + char *on_off_name = event_name_from_id(database, req_tracer_msgs_indices[i]); + on_off(database, on_off_name, is_on, 0); + // printf("\n %d, %s, ", req_tracer_msgs_indices[i], on_off_name); + // on_off(database, "GNB_PHY_DL_OUTPUT_SIGNAL", is_on, 1); + } + // De-activate the tracee in the nr-softmodem + activate_traces(socket, number_of_events, is_on); + printf("\n De-activated Traces"); + // Get the end time + struct timespec end_time = get_current_time(); + // Calculate the time difference + double time_diff = calculate_time_difference(start_time, end_time); + printf("Total Time difference: %.2f ms\n", time_diff); + printf("Time difference per record: %.2f ms\n", time_diff / (number_records + max_sync_offset)); + + // discard stale or previous record data for the first DISCARD_RECORD_DURATION_MS + struct timespec record_start, record_now; + clock_gettime(CLOCK_MONOTONIC, &record_start); + + while (1) { + clock_gettime(CLOCK_MONOTONIC, &record_now); + double elapsed_ms = calculate_time_difference(record_start, record_now); + if (elapsed_ms >= DISCARD_RECORD_DURATION_MS) { + break; // Stop after 10ms + } + int poll_ret = poll(&event_poll_fd, 1, 1); // 1 ms timeout for poll + if (poll_ret > 0 && (event_poll_fd.revents & POLLIN)) { + event e = get_event(socket, &ebuf, database); + printf("%d", e.type); + if (e.type == -1) { + printf("\n Link broken or unexpected message received. Re-establishing connection...\n"); + reestablish_connection(&socket, ip, port, number_of_events, is_on); + continue; + } + } else { + // No data, just loop and check time + usleep(100); // optional: avoid busy-waiting + } + } // End of while loop to discard stale records + } + } // End a while loop to check for the "stop" command + // de-activate the tracee in the nr-softmodem + + free_database(database); + free(is_on); + close(socket); + + return 0; +} diff --git a/common/utils/T/tracer/t_tracer_app_ue.c b/common/utils/T/tracer/t_tracer_app_ue.c new file mode 100644 index 0000000000..141bbd2385 --- /dev/null +++ b/common/utils/T/tracer/t_tracer_app_ue.c @@ -0,0 +1,976 @@ +/* + * 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 common/utils/T/tracer/t_tracer_app_ue.c + * \brief T-Tracer UE service to capture tracee Messages from UE, it is used by data recording application + * \author Abdo Gaber + * \date 2025 + * \version 1.0 + * \company Emerson, NI Test and Measurement + * \email: + * \note + * \warning + */ + +#include +#include +#include +#include +#include +#include "database.h" +#include "event.h" +#include "handler.h" +#include "logger/logger.h" +#include "utils.h" +#include "event_selector.h" +#include "configuration.h" +#include "shared_memory_config.h" +#include +#include +#include +#include +#include +#include +#include +#define MAX_FRAME_INDEX 1023 +#define MAX_SLOT_INDEX 19 +#define DEBUG_T_Tracer +#define DEBUG_BUFFER +// Duration to discard recording in milliseconds to mitigate stale or previous record data +#define DISCARD_RECORD_DURATION_MS 10 +// #include + +// Combine bytes in little-endian format +int combine_bytes(const uint8_t *bytes, size_t num_bytes) +{ + int result = 0; + for (size_t i = 0; i < num_bytes; ++i) { + result |= (bytes[i] << (i * 8)); + } + return result; +} + +// Convert an integer to an array of bytes in little-endian format +void int_to_bytes(int num, uint8_t *bytes, size_t num_bytes) +{ + for (size_t i = 0; i < num_bytes; ++i) { + bytes[i] = (num >> (i * 8)) & 0xFF; + } +} + +// Check if the message is in the list of bits messages +bool is_bits_messages(int traces_bits_support_data_Collection_format_idx[], int n_bits_msgs, int msg_id) +{ + for (int i = 0; i < n_bits_msgs; i++) { + if (msg_id == traces_bits_support_data_Collection_format_idx[i]) { + return true; + } + } + return false; +} + +// Get the current time +struct timespec get_current_time() +{ + struct timespec time; + clock_gettime(CLOCK_MONOTONIC, &time); + return time; +} + +// Function to convert timespec to microseconds +long long timespec_to_microseconds(struct timespec time) +{ + return (time.tv_sec * 1000000LL) + (time.tv_nsec / 1000); +} + +// Calculate the time difference in milliseconds +double calculate_time_difference(struct timespec start, struct timespec end) +{ + double start_ms = start.tv_sec * 1000.0 + start.tv_nsec / 1000000.0; + double end_ms = end.tv_sec * 1000.0 + end.tv_nsec / 1000000.0; + return end_ms - start_ms; +} + +// Get Time Stamp in microseconds in YYYYMMDDHHMMSSmmmuuu format +char *get_time_stamp_usec(char time_stamp_str[]) +{ + // initialization to measure time stamp --This part should be moved to inilization part + time_t my_time; + struct tm *timeinfo; + time(&my_time); + struct timeval tv; + + // get time stamp + timeinfo = localtime(&my_time); + gettimeofday(&tv, NULL); + // Add time stamp: yyyy mm dd hh mm ss usec + uint16_t year = timeinfo->tm_year + 1900; + uint8_t mon = timeinfo->tm_mon + 1; + uint8_t mday = timeinfo->tm_mday; + uint8_t hour = timeinfo->tm_hour; + uint8_t min = timeinfo->tm_min; + uint8_t sec = timeinfo->tm_sec; + uint16_t usec = (tv.tv_usec); + // printf ("Time stamp: %d_%d_%d_%d_%d_%d_%d \n",year,mon,mday,hour,min,sec,usec); + // sprintf(time_stamp_str, "%d_%d_%d_%d_%d_%d_%d",year,mon,mday,hour,min,sec,usec); + sprintf(time_stamp_str, "%04d%02d%02d%02d%02d%02d%06d", year, mon, mday, hour, min, sec, usec); + + return time_stamp_str; +} + +// Convert timestamp string to integer +int convert_time_stamp_to_int(const char *timestamp) +{ + return atoi(timestamp); +} + +// Split timestamp string and convert to integer +int split_time_stamp_and_convert_to_int(char time_stamp_str[], int shift, int length) +{ + char time_part[length + 1]; // Buffer to hold the date part YYYYMMDD or HHMMSSmmm + // Copy the first 8 or 9 characters (YYYYMMDD) to HHMMSSmmm + strncpy(time_part, time_stamp_str + shift, length); + time_part[length] = '\0'; // Null-terminate the string + // Convert timestamp string to integer + return convert_time_stamp_to_int(time_part); +} + +void err_exit(char *buf) +{ + fprintf(stderr, "%s\n", buf); + exit(1); +} + +// create shared memory +int create_shm(char **addrN, const char *shm_path, int projectId) +{ + key_t key; + if (-1 != open(shm_path, O_CREAT, 0777)) { + key = ftok(shm_path, projectId); + } else { + err_exit("Error: open shared memory"); + } + + if (key < 0) { + err_exit("Error: ftok error"); + } + + int shm_id; + shm_id = shmget(key, SHMSIZE, IPC_CREAT | IPC_EXCL | 0664); + if (shm_id == -1) { + if (errno == EEXIST) { + printf("Error: shared memeory already exist\n"); + shm_id = shmget(key, 0, 0); + printf("reference shm_id = %d\n", shm_id); + } else { + perror("errno"); + err_exit("shmget error"); + } + } + char *addr; + // address to attach - attach for read & write + if ((addr = shmat(shm_id, 0, 0)) == (void *)-1) { + if (shmctl(shm_id, IPC_RMID, NULL) == -1) + err_exit("Error: shmctl error"); + else { + printf("Attach shared memory failed\n"); + printf("remove shared memory identifier successful\n"); + } + err_exit("shmat error"); + } + *addrN = addr; + return shm_id; +} + +// delete shared memory +void del_shm(char *addr, int shm_id) +{ + if (shmdt(addr) < 0) + err_exit("shmdt error"); + + if (shmctl(shm_id, IPC_RMID, NULL) == -1) + err_exit("shmctl error"); + else { + printf("Remove shared memory identifier successful\n"); + } +} + +/* this function sends the activated traces to the nr-softmodem */ +void activate_traces(int socket, int number_of_events, int *is_on) +{ + char t = 1; + if (socket_send(socket, &t, 1) == -1 || socket_send(socket, &number_of_events, sizeof(int)) == -1 + || socket_send(socket, is_on, number_of_events * sizeof(int)) == -1) + abort(); +} + +void usage(void) +{ + printf( + "options:\n" + " -d this option is mandatory\n" + " -ip connect to given IP address (default %s)\n" + " -p connect to given port (default %d)\n", + DEFAULT_REMOTE_IP, + DEFAULT_REMOTE_PORT); + exit(1); +} + +// class to store the message and the action to enable capture selected data +// 0: do not record message +// 1: record message +struct trace_struct { + char on_off_name[100]; + int on_off_action; +}; + +// struct for trace message based on Data Collection Trace Messages Structure +// you need to define the vararibles of each message +typedef struct { + /* Data Collection Trace Message Structure */ + int frame; + int slot; + int datetime_yyyymmdd; + int datetime_hhmmssmmm; + int frame_type, freq_range, subcarrier_spacing, cyclic_prefix, symbols_per_slot; + int Nid_cell, rnti; + int rb_size, rb_start, start_symbol_index, nr_of_symbols; + int qam_mod_order, mcs_index, mcs_table, nrOfLayers, transform_precoding; + int dmrs_config_type, ul_dmrs_symb_pos, number_dmrs_symbols; + int dmrs_port, dmrs_nscid, nb_antennas_tx, number_of_bits; + int data_size, data; +} event_trace_msg_data; + +void setup_trace_msg_data(event_trace_msg_data *d, void *database) +{ + database_event_format f; + int i; + + /* Data Collection Trace Message Structure */ + // FORMAT = int,frame : int,slot : int,datetime_yyyymmdd : int,datetime_hhmmssmmm : + // int,frame_type : int,freq_range : int,subcarrier_spacing : int,cyclic_prefix : int,symbols_per_slot : + // int,Nid_cell : int,rnti : + // int,rb_size : int,rb_start : int,start_symbol_index : int,nr_of_symbols : + // int,qam_mod_order : int,mcs_index : int,mcs_table : int,nrOfLayers : + // int,transform_precoding : int,dmrs_config_type : int,ul_dmrs_symb_pos : int,number_dmrs_symbols : + // int,dmrs_port : int,dmrs_nscid : + // int,nb_antennas_tx : int,number_of_bits : buffer,data + + // Initialize the data structure + d->frame = -1; + d->slot = -1; + d->datetime_yyyymmdd = -1; + d->datetime_hhmmssmmm = -1; + d->frame_type = -1; + d->freq_range = -1; + d->subcarrier_spacing = -1; + d->cyclic_prefix = -1; + d->symbols_per_slot = -1; + d->Nid_cell = -1; + d->rnti = -1; + d->rb_size = -1; + d->rb_start = -1; + d->start_symbol_index = -1; + d->nr_of_symbols = -1; + d->qam_mod_order = -1; + d->mcs_index = -1; + d->mcs_table = -1; + d->nrOfLayers = -1; + d->transform_precoding = -1; + d->dmrs_config_type = -1; + d->ul_dmrs_symb_pos = -1; + d->number_dmrs_symbols = -1; + d->dmrs_port = -1; + d->dmrs_nscid = -1; + d->nb_antennas_tx = -1; + d->number_of_bits = -1; + d->data = -1; + +/* this macro looks for a particular element and checks its type */ +#define G(var_name, var_type, var) \ + if (!strcmp(f.name[i], var_name)) { \ + if (strcmp(f.type[i], var_type)) { \ + printf("Error: Trace Message has a bad type for %s\n", var_name); \ + exit(1); \ + } \ + var = i; \ + continue; \ + } + + /* ------------------------------*/ + /* Create Macro for Data Collection Trace Message */ + /* ------------------------------*/ + // Data Collection Trace Message Structure + // Example: GNB_PHY_UL_FD_PUSCH_IQ, GNB_PHY_UL_FD_DMRS_ID, + // GNB_PHY_UL_FD_CHAN_EST_DMRS_POS, GNB_PHY_UL_FD_CHAN_EST_DMRS_INTERPL + // GNB_PHY_UL_PAYLOAD_RX_BITS + // UE_PHY_UL_SCRAMBLED_TX_BITS + // UE_PHY_UL_PAYLOAD_TX_BITS + // Get a template of any message based on Data Collection Trace Messages Structure + int Trace_MSG_ID = event_id_from_name(database, "GNB_PHY_UL_FD_PUSCH_IQ"); + f = get_format(database, Trace_MSG_ID); + + /* get the elements of the trace + * the value is an index in the event, see below */ + // FORMAT = int,frame : int,slot : int,datetime_yyyymmdd : int,datetime_hhmmssmmm : + // int,frame_type : int,freq_range : int,subcarrier_spacing : int,cyclic_prefix : int,symbols_per_slot : + // int,Nid_cell : int,rnti : + // int,rb_size : int,rb_start : int,start_symbol_index : int,nr_of_symbols : + // int,qam_mod_order : int,mcs_index : int,mcs_table : int,nrOfLayers : + // int,transform_precoding : int,dmrs_config_type : int,ul_dmrs_symb_pos : int,number_dmrs_symbols : + // int,dmrs_port : int,dmrs_nscid : + // int,nb_antennas_tx : int,number_of_bits : buffer,data + + for (i = 0; i < f.count; i++) { + G("frame", "int", d->frame) + G("slot", "int", d->slot) + G("datetime_yyyymmdd", "int", d->datetime_yyyymmdd) + G("datetime_hhmmssmmm", "int", d->datetime_hhmmssmmm) + G("frame_type", "int", d->frame_type) + G("freq_range", "int", d->freq_range) + G("subcarrier_spacing", "int", d->subcarrier_spacing) + G("cyclic_prefix", "int", d->cyclic_prefix) + G("symbols_per_slot", "int", d->symbols_per_slot) + G("Nid_cell", "int", d->Nid_cell) + G("rnti", "int", d->rnti) + G("rb_size", "int", d->rb_size) + G("rb_start", "int", d->rb_start) + G("start_symbol_index", "int", d->start_symbol_index) + G("nr_of_symbols", "int", d->nr_of_symbols) + G("qam_mod_order", "int", d->qam_mod_order) + G("mcs_index", "int", d->mcs_index) + G("mcs_table", "int", d->mcs_table) + G("nrOfLayers", "int", d->nrOfLayers) + G("transform_precoding", "int", d->transform_precoding) + G("dmrs_config_type", "int", d->dmrs_config_type) + G("ul_dmrs_symb_pos", "int", d->ul_dmrs_symb_pos) + G("number_dmrs_symbols", "int", d->number_dmrs_symbols) + G("dmrs_port", "int", d->dmrs_port) + G("dmrs_nscid", "int", d->dmrs_nscid) + // bug in T-Tracer Framework, need to be checked that is why we are getting + // the right tx antennas only if we set to rx "nb_antennas_rx" + G("nb_antennas_rx", "int", d->nb_antennas_tx) + G("number_of_bits", "int", d->number_of_bits) + G("data", "buffer", d->data) + } + // if (d->frame == -1 || d->slot == -1) goto error; +#undef G + return; +} + +// Function to check if a value is in the array +int isValueInArray(int value, int arr[], int size) +{ + for (int i = 0; i < size; i++) { + if (arr[i] == value) { + return 1; // Value found + } + } + return 0; // Value not found +} + +void reestablish_connection(int *socket, char *ip, int port, int number_of_events, int *is_on) +{ + clear_remote_config(); + if (*socket != -1) + close(*socket); + + /* connect to the nr-softmodem */ + *socket = connect_to(ip, port); + if (*socket == -1) { + printf("\n Failed to connect to nr-softmodem. Retrying...\n"); + return; + } + printf("\n Connected"); + + /* activate the traces in the nr-softmodem */ + activate_traces(*socket, number_of_events, is_on); + printf("\n Activated Traces in nr-softmodem"); +} + +int main(int n, char **v) +{ + // Define and initialize an array of strings to list + // trace_msgs_support_data_Collection_format + // it is used to parse the requested messages if it is based + // on Data Collection Trace Messages Structure and supported tracer messages indices + char *traces_iq_support_data_Collection_format[] = {"GNB_PHY_UL_FD_PUSCH_IQ", + "GNB_PHY_UL_FD_DMRS", + "GNB_PHY_UL_FD_CHAN_EST_DMRS_POS", + "GNB_PHY_UL_FD_CHAN_EST_DMRS_INTERPL"}; + char *traces_bits_support_data_Collection_format[] = {"GNB_PHY_UL_PAYLOAD_RX_BITS", + "UE_PHY_UL_SCRAMBLED_TX_BITS", + "UE_PHY_UL_PAYLOAD_TX_BITS"}; + // extra number of records to simlify Sync between base station and UE synchronization records. + // if you have network delay, you can increase the number of records to capture + int max_sync_offset = 6; // 6 frames ~ 60 ms + // all supported messages + // Calculate the size of the combined array + int n_iq_msgs = sizeof(traces_iq_support_data_Collection_format) / sizeof(traces_iq_support_data_Collection_format[0]); + int n_bits_msgs = sizeof(traces_bits_support_data_Collection_format) / sizeof(traces_bits_support_data_Collection_format[0]); + int n_msgs_based_data_Collection_format = n_iq_msgs + n_bits_msgs; + + // Create the combined array + char *traces_support_data_Collection_format[n_msgs_based_data_Collection_format]; + // Copy IQ messages to the combined array + for (int i = 0; i < n_iq_msgs; i++) { + traces_support_data_Collection_format[i] = traces_iq_support_data_Collection_format[i]; + } + + // Copy Bits messages to the combined array + for (int i = 0; i < n_bits_msgs; i++) { + traces_support_data_Collection_format[i + n_iq_msgs] = traces_bits_support_data_Collection_format[i]; + } + + uint16_t msg_id = 0; + uint16_t start_frame_number = 0; + uint32_t number_records = 0; // number of records to capture, it is number of slots + // array to store the requested tracer messages indices + int req_tracer_msgs_indices[100] = {0}; + // define variables --> to do: add all of them to be class of pointers + char *database_filename = NULL; + void *database; + char *ip = DEFAULT_REMOTE_IP; + char ip_address[16]; // max IP address length is 15 + 1 for null terminator + int port = DEFAULT_REMOTE_PORT; + int *is_on; + int number_of_events; + int i; + int socket = -1; + char trace_time_stamp_str[30]; + + // data structure for the trace messages based on Data Collection Trace Messages Structure + event_trace_msg_data trace_msg_data; + + // initlization variables + unsigned int bufIdx_wr = 0; + unsigned int bufIdx_rd = 0; + uint8_t num_req_tracer_msgs = 0; + + // initilaze shared memory + char *addr_wr, *addr_rd; + printf("\n Data Collection Service: Initializing shared memory ..."); + printf("\n Directory 1: %s, Directory 2: %s", GETKEYDIR1_UE, GETKEYDIR2_UE); + printf("\n Project ID: %d\n", PROJECTID_UE); + int shm_id_wr = create_shm(&addr_wr, GETKEYDIR1_UE, PROJECTID_UE); + int shm_id_rd = create_shm(&addr_rd, GETKEYDIR2_UE, PROJECTID_UE); + del_shm(addr_wr, shm_id_wr); + del_shm(addr_rd, shm_id_rd); + shm_id_wr = create_shm(&addr_wr, GETKEYDIR1_UE, PROJECTID_UE); + shm_id_rd = create_shm(&addr_rd, GETKEYDIR2_UE, PROJECTID_UE); + + /* write on a socket fails if the other end is closed and we get SIGPIPE */ + if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) + abort(); + + /* parse command line options */ + // Port number and IP address are given via API and not via command line + for (i = 1; i < n; i++) { + if (!strcmp(v[i], "-h") || !strcmp(v[i], "--help")) + usage(); + if (!strcmp(v[i], "-d")) { + if (i > n - 2) + usage(); + database_filename = v[++i]; + continue; + } + if (!strcmp(v[i], "-ip")) { + if (i > n - 2) + usage(); + ip = v[++i]; + continue; + } + if (!strcmp(v[i], "-p")) { + if (i > n - 2) + usage(); + port = atoi(v[++i]); + continue; + } + usage(); + } + + if (database_filename == NULL) { + printf("ERROR: Provide a database file (-d)\n"); + exit(1); + } + + /* load the database T_messages.txt */ + database = parse_database(database_filename); + load_config_file(database_filename); + + /* an array of int for all the events defined in the database is needed */ + number_of_events = number_of_ids(database); + is_on = calloc(number_of_events, sizeof(int)); + if (is_on == NULL) + abort(); + + // Set first byte of the shared memory to 0 + addr_rd[0] = 0; // important to check if we have new requested messages + + // read requested tracer msg indices from memory + printf("\n Data Collection Service: Waiting for messages request ...\n"); + // 0: Wait + // 1: config + // 2: record + // 3: stop + // Wait for Action Config + while (1) { + if ((uint8_t)(addr_rd[0]) == 0) { + usleep(50); // sleep for 50 us: 0.5 ms slot duration + } + // config state + else if ((uint8_t)(addr_rd[0]) == 1) { + get_time_stamp_usec(trace_time_stamp_str); + printf("\n Received config message. Time Stamp: %s", trace_time_stamp_str); + + // get the IP address length in bytes + bufIdx_rd = 1; + uint8_t ip_address_length = addr_rd[bufIdx_rd]; + bufIdx_rd += 1; // + 1 byte = IP address length + + // read the IP address + for (int i = 0; i < ip_address_length; i++) { + ip_address[i] = addr_rd[bufIdx_rd]; + bufIdx_rd += 1; + } + + ip = ip_address; + + // get the array bytes of the port number : 2 bytes + uint8_t port_number_bytes[2] = {addr_rd[bufIdx_rd], addr_rd[bufIdx_rd + 1]}; + bufIdx_rd += 2; // + 2 bytes = start frame number + + port = combine_bytes(port_number_bytes, 2); + printf("\n Parameters: IP Address length: %d, IP Address: %s, Port Number: %d \n", ip_address_length, ip_address, port); + addr_rd[0] = 0; // reset memory : to wait for record action + break; + } + } + // Establish connection to the nr-softmodem + clear_remote_config(); + if (socket != -1) + close(socket); + + /* connect to the nr-softmodem */ + socket = connect_to(ip, port); + printf("\n Connected to nr-UEsoftmodem"); + + // Read Action record or stop + printf("\n Data Collection Service: Waiting for record message ...\n"); + while (1) { + // wait for Action record + if ((uint8_t)(addr_rd[0]) == 0) { + usleep(50); // sleep for 50 us: 0.5 ms slot duration + } + + // quit state + else if ((uint8_t)(addr_rd[0]) == 3) { + printf("\n Received 'stop' command. Exiting..."); + printf("\n "); + // Clean up and exit + break; + } + // record state + else if ((uint8_t)(addr_rd[0]) == 2) { + // clear remote buffer if there is + clear_remote_config(); + get_time_stamp_usec(trace_time_stamp_str); + printf("\n Received record message. Time Stamp: %s", trace_time_stamp_str); + + // read number of requested messages + bufIdx_rd = 1; + num_req_tracer_msgs = addr_rd[bufIdx_rd]; + bufIdx_rd += 1; + printf("\n Number of requested tracer messages: %d,", num_req_tracer_msgs); + // reset memory : action to wait for next record action + addr_rd[0] = 0; + + // read tracer msg IDs - every message ID has been stored in two bytes + for (uint8_t msg_n = 0; msg_n < num_req_tracer_msgs; msg_n++) { + // get the array bytes of the tracer message ID: 2 bytes + uint8_t msg_id_bytes[2] = {addr_rd[bufIdx_rd], addr_rd[bufIdx_rd + 1]}; + bufIdx_rd += 2; // + 2 bytes = message ID + msg_id = combine_bytes(msg_id_bytes, 2); + + req_tracer_msgs_indices[msg_n] = msg_id; + printf(" msg_id: %d, ", msg_id); + } + // get the array bytes of the number of records: 4 bytes + uint8_t number_records_bytes[4] = {addr_rd[bufIdx_rd], + addr_rd[bufIdx_rd + 1], + addr_rd[bufIdx_rd + 2], + addr_rd[bufIdx_rd + 3]}; + bufIdx_rd += 4; // + 4 bytes = number of records + + number_records = combine_bytes(number_records_bytes, 4); + + printf("num_records: %d, ", number_records); + + // get the array bytes of the start frame number : 2 bytes + uint8_t start_frame_number_bytes[2] = {addr_rd[bufIdx_rd], addr_rd[bufIdx_rd + 1]}; + bufIdx_rd += 2; // + 2 bytes = start frame number + + start_frame_number = combine_bytes(start_frame_number_bytes, 2); + + printf("start_frame: %d\n", start_frame_number); + + /* activate the trace in this array */ + printf("\n Activate Tracer messages in on_off array: \n"); + for (i = 0; i < num_req_tracer_msgs; i++) { + char *on_off_name = event_name_from_id(database, req_tracer_msgs_indices[i]); + on_off(database, on_off_name, is_on, 1); + printf("%d, %s, ", req_tracer_msgs_indices[i], on_off_name); + // on_off(database, "GNB_PHY_DL_OUTPUT_SIGNAL", is_on, 1); + } + + // get the event IDs for the bit messages + int traces_bits_support_data_Collection_format_idx[n_bits_msgs]; + for (int i = 0; i < n_bits_msgs; i++) { + traces_bits_support_data_Collection_format_idx[i] = + event_id_from_name(database, traces_bits_support_data_Collection_format[i]); + } + + // all supported messages + int traces_support_data_Collection_format_idx[n_msgs_based_data_Collection_format]; + for (int i = 0; i < n_msgs_based_data_Collection_format; i++) { + traces_support_data_Collection_format_idx[i] = event_id_from_name(database, traces_support_data_Collection_format[i]); + } + + // setup data for the trace messages + setup_trace_msg_data(&trace_msg_data, database); + printf("\n Setup Trace Data Done"); + + // Get the start time + struct timespec start_time = get_current_time(); + + /* activate the tracee in the nr-softmodem */ + activate_traces(socket, number_of_events, is_on); + printf("\n Activated Traces in nr-UEsoftmodem"); + + /* a buffer needed to receive events from the nr-softmodem */ + OBUF ebuf = {osize : 0, omaxsize : 0, obuf : NULL}; + /* read events */ + int nrecord_idx = 0; + bufIdx_wr = 0; + // logic to be sure that we have a complete slot record + bool start_recording = false; + bool got_ref_frame_slot = false; + int ref_slot = 0; + int ref_frame = 0; + int slot_difference = 0; + int frame_difference = 0; + int current_frame = 0, prev_frame = 0, current_slot = 0, prev_slot = 0; + // offset to sync between base station and UE synchronization records or power measurements + int sync_offset_index = 0; // increase the index only if the index of frame changes after getting all records + // since we will use the frame differece to do extra records, we should be sure that the last slot is recorded completely + printf("\n\n Data Collection Service: Start reading messages ..."); + struct pollfd event_poll_fd; + event_poll_fd.fd = socket; + event_poll_fd.events = POLLIN; + while (1) { + // stop if number of records is done + if ((nrecord_idx >= number_records) && (sync_offset_index >= max_sync_offset)) { + // We added one to the number of records to capture the last record completely + // if we have several messages enabled per slot + break; + } + int poll_ret = poll(&event_poll_fd, 1, 1); // 1 ms timeout for poll + if (poll_ret > 0 && (event_poll_fd.revents & POLLIN)) { + event e = get_event(socket, &ebuf, database); + if (e.type == -1) { + printf("\n Link broken or unexpected message received. Re-establishing connection...\n"); + reestablish_connection(&socket, ip, port, number_of_events, is_on); + continue; // Skip further processing and retry + } + + //------------------------- + // GNB_PHY_UL_FD_PUSCH_IQ, GNB_PHY_UL_FD_DMRS_ID, GNB_PHY_UL_FD_CHAN_EST_DMRS_POS, + // UE_PHY_UL_SCRAMBLED_TX_BITS, GNB_PHY_UL_PAYLOAD_RX_BITS, UE_PHY_UL_PAYLOAD_TX_BITS + //------------------------- + // is it a requested message + if (isValueInArray(e.type, req_tracer_msgs_indices, num_req_tracer_msgs)) { + // is it based on Data Collection Trace Messages Structure + if (isValueInArray(e.type, traces_support_data_Collection_format_idx, n_msgs_based_data_Collection_format)) { + // Start recording from the next slot to mitigate capturing partial data + // check if the current frame and slot are different from the previous frame and slot + // Then, increase the record index + if (start_recording == false) { + if (got_ref_frame_slot == false) { + ref_frame = e.e[trace_msg_data.frame].i; + ref_slot = e.e[trace_msg_data.slot].i; + printf("\nMessage Info: msg_id %s (%d) \n", event_name_from_id(database, e.type), e.type); + got_ref_frame_slot = true; + } + + current_frame = e.e[trace_msg_data.frame].i; + current_slot = e.e[trace_msg_data.slot].i; + frame_difference = (current_frame - ref_frame + MAX_FRAME_INDEX + 1) % (MAX_FRAME_INDEX + 1); + slot_difference = (current_slot - ref_slot + MAX_SLOT_INDEX + 1) % (MAX_SLOT_INDEX + 1); + printf("\n First frame.slot: %d.%d, current frame.slot: %d.%d, diff frame.slot: %d.%d", + ref_frame, + ref_slot, + current_frame, + current_slot, + frame_difference, + slot_difference); + + if ((ref_frame != current_frame) || (ref_slot != current_slot)) { + start_recording = true; + printf("\n Start recording from frame: %d, slot: %d ", e.e[trace_msg_data.frame].i, e.e[trace_msg_data.slot].i); + } + } + // start recording from the next frame to mitigate capturing partial data + if (start_recording == true) { + /* this is how to access the elements of the Data Collection trace messages. + * we use e.e[] and then the correct suffix, here + * it's .i for the integer and .b for the buffer and .bsize for the buffer size + * see in event.h the structure event_arg + */ + unsigned char *buf = e.e[trace_msg_data.data].b; + + printf("\n\nRecord number: %d", nrecord_idx); +#ifdef DEBUG_BUFFER + printf("\nBuffer index in bytes: %d", bufIdx_wr); +#endif + + // add general message header: message ID, + // T-Tracer Message format + // FORMAT = int,frame : int,slot : int,datetime_yyyymmdd : int,datetime_hhmmssmmm : + // int,frame_type : int,freq_range : int,subcarrier_spacing : int,cyclic_prefix : int,symbols_per_slot : + // int,Nid_cell : int,rnti : + // int,rb_size : int,rb_start : int,start_symbol_index : int,nr_of_symbols : + // int,qam_mod_order : int,mcs_index : int,mcs_table : int,nrOfLayers : + // int,transform_precoding : int,dmrs_config_type : int,ul_dmrs_symb_pos : int,number_dmrs_symbols : + // int,dmrs_port : int,dmrs_nscid : + // int,nb_antennas_tx : int,number_of_bits : buffer,data + // printf("\nTX : %d", e.e[trace_msg_data.nb_antennas_tx].i); + memcpy(&addr_wr[bufIdx_wr], &e.type, sizeof(uint16_t)); + bufIdx_wr += sizeof(uint16_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.frame].i, sizeof(uint16_t)); + bufIdx_wr += sizeof(uint16_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.slot].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.datetime_yyyymmdd].i, sizeof(uint32_t)); + bufIdx_wr += sizeof(uint32_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.datetime_hhmmssmmm].i, sizeof(uint32_t)); + bufIdx_wr += sizeof(uint32_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.frame_type].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.freq_range].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.subcarrier_spacing].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.cyclic_prefix].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.symbols_per_slot].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.Nid_cell].i, sizeof(uint16_t)); + bufIdx_wr += sizeof(uint16_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.rnti].i, sizeof(uint16_t)); + bufIdx_wr += sizeof(uint16_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.rb_size].i, sizeof(uint16_t)); + bufIdx_wr += sizeof(uint16_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.rb_start].i, sizeof(uint16_t)); + bufIdx_wr += sizeof(uint16_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.start_symbol_index].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.nr_of_symbols].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.qam_mod_order].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.mcs_index].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.mcs_table].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.nrOfLayers].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.transform_precoding].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.dmrs_config_type].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.ul_dmrs_symb_pos].i, sizeof(uint16_t)); + bufIdx_wr += sizeof(uint16_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.number_dmrs_symbols].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.dmrs_port].i, sizeof(uint16_t)); + bufIdx_wr += sizeof(uint16_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.dmrs_nscid].i, sizeof(uint16_t)); + bufIdx_wr += sizeof(uint16_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.nb_antennas_tx].i, sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.number_of_bits].i, sizeof(uint32_t)); + bufIdx_wr += sizeof(uint32_t); + + printf("\nTime Stamp: %d_%d", e.e[trace_msg_data.datetime_yyyymmdd].i, e.e[trace_msg_data.datetime_hhmmssmmm].i); + + // add message body: length in bytes + recorded data + memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.data].bsize, sizeof(uint32_t)); + bufIdx_wr += sizeof(uint32_t); + + // read data from buffer and convert from unsigned char * array to int 16 using the right endianness + // T-tracer: Little Endian + // For BITS Messages, example: UE_PHY_UL_SCRAMBLED_TX_BITS: data in bytes + + if (is_bits_messages(traces_bits_support_data_Collection_format_idx, n_bits_msgs, e.type)) { + for (int byte_idx = 0; byte_idx < e.e[trace_msg_data.data].bsize; byte_idx += 1) { + // printf("%d, ", buf[byte_idx]); + memcpy(&addr_wr[bufIdx_wr], &buf[byte_idx], sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + } + + } else { + for (int byte_idx = 0; byte_idx < e.e[trace_msg_data.data].bsize; byte_idx += 2) { + // For a little-endian system: + memcpy(&addr_wr[bufIdx_wr], &buf[byte_idx], sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + memcpy(&addr_wr[bufIdx_wr], &buf[byte_idx + 1], sizeof(uint8_t)); + bufIdx_wr += sizeof(uint8_t); + } + } + /* + for (int byte_idx_i = 0; byte_idx_i < e.e[d.nr_ul_fd_dmrs_data].bsize; byte_idx_i+=4) { + int16_t I = buf[byte_idx_i] | (buf[byte_idx_i+1] << 8); + int16_t Q = buf[byte_idx_i+2] | (buf[byte_idx_i+3] << 8); + printf ("idx %d, ", byte_idx_i); + printf ("\n%d", I); + printf ("\n%d", Q); + } + */ + // check if the current frame and slot are different from the previous frame and slot + // Then, increase the record index + current_frame = e.e[trace_msg_data.frame].i; + current_slot = e.e[trace_msg_data.slot].i; + + // increase sync offset index if the current frame is different from the previous frame + if ((current_frame != prev_frame) && nrecord_idx >= number_records) { + sync_offset_index++; + } + + if (current_frame != prev_frame || current_slot != prev_slot) { + nrecord_idx++; + // Update previous frame and slot numbers + prev_frame = current_frame; + prev_slot = current_slot; + } +#ifdef DEBUG_T_Tracer + printf("\nMessage Info: msg_id %s (%d) \n", event_name_from_id(database, e.type), e.type); + printf("frame %d, slot %d, datetime %d_%d\n", + e.e[trace_msg_data.frame].i, + e.e[trace_msg_data.slot].i, + e.e[trace_msg_data.datetime_yyyymmdd].i, + e.e[trace_msg_data.datetime_hhmmssmmm].i); + printf("frame_type %d, freq_range %d, subcarrier_spacing %d, cyclic_prefix %d, symbols_per_slot %d\n", + e.e[trace_msg_data.frame_type].i, + e.e[trace_msg_data.freq_range].i, + e.e[trace_msg_data.subcarrier_spacing].i, + e.e[trace_msg_data.cyclic_prefix].i, + e.e[trace_msg_data.symbols_per_slot].i); + printf("Nid_cell %d, rnti %d, rb_size %d, rb_start %d, start_symbol_index %d, nr_of_symbols %d\n", + e.e[trace_msg_data.Nid_cell].i, + e.e[trace_msg_data.rnti].i, + e.e[trace_msg_data.rb_size].i, + e.e[trace_msg_data.rb_start].i, + e.e[trace_msg_data.start_symbol_index].i, + e.e[trace_msg_data.nr_of_symbols].i); + printf("qam_mod_order %d, mcs_index %d, mcs_table %d, nrOfLayers %d, transform_precoding %d\n", + e.e[trace_msg_data.qam_mod_order].i, + e.e[trace_msg_data.mcs_index].i, + e.e[trace_msg_data.mcs_table].i, + e.e[trace_msg_data.nrOfLayers].i, + e.e[trace_msg_data.transform_precoding].i); + printf("dmrs_config_type %d, ul_dmrs_symb_pos %d, number_dmrs_symbols %d, dmrs_port %d, dmrs_nscid %d\n", + e.e[trace_msg_data.dmrs_config_type].i, + e.e[trace_msg_data.ul_dmrs_symb_pos].i, + e.e[trace_msg_data.number_dmrs_symbols].i, + e.e[trace_msg_data.dmrs_port].i, + e.e[trace_msg_data.dmrs_nscid].i); + printf("nb_antennas_tx %d, number_of_bits %d, data size %d\n", + e.e[trace_msg_data.nb_antennas_tx].i, + e.e[trace_msg_data.number_of_bits].i, + e.e[trace_msg_data.data].bsize); +#endif + } // End of start recording flag + } // end of if statement for the supported messages based on Data Collection Trace Messages Structure + else { + printf("ERROR: Requested Message is not based on Data Collection Trace Messages Structure\n"); + printf("ERROR: Requested Message ID: %d\n", e.type); + } + } // end of if statement for the requested messages + } // end while loop of reading events + else { + // No data, just loop and check time + usleep(100); // optional: avoid busy-waiting + } + } // End of while loop to read messages + + // de-activate the tracee in the nr-softmodem + printf("\n De-activated Tracer message:\n"); + for (i = 0; i < num_req_tracer_msgs; i++) { + char *on_off_name = event_name_from_id(database, req_tracer_msgs_indices[i]); + on_off(database, on_off_name, is_on, 0); + // printf("\n %d, %s, ", req_tracer_msgs_indices[i], on_off_name); + // on_off(database, "GNB_PHY_DL_OUTPUT_SIGNAL", is_on, 1); + } + // De-activate the tracee in the nr-softmodem + activate_traces(socket, number_of_events, is_on); + printf("\n De-activated Traces"); + + // Get the end time + struct timespec end_time = get_current_time(); + // Calculate the time difference + double time_diff = calculate_time_difference(start_time, end_time); + printf("Total Time difference: %.2f ms\n", time_diff); + printf("Time difference per record: %.2f ms\n", time_diff / (number_records + max_sync_offset)); + + // discard stale or previous record data for the first DISCARD_RECORD_DURATION_MS + struct timespec record_start, record_now; + clock_gettime(CLOCK_MONOTONIC, &record_start); + + while (1) { + clock_gettime(CLOCK_MONOTONIC, &record_now); + double elapsed_ms = calculate_time_difference(record_start, record_now); + if (elapsed_ms >= DISCARD_RECORD_DURATION_MS) { + break; // Stop after 10ms + } + int poll_ret = poll(&event_poll_fd, 1, 1); // 1 ms timeout for poll + if (poll_ret > 0 && (event_poll_fd.revents & POLLIN)) { + event e = get_event(socket, &ebuf, database); + printf("%d", e.type); + if (e.type == -1) { + printf("\n Link broken or unexpected message received. Re-establishing connection...\n"); + reestablish_connection(&socket, ip, port, number_of_events, is_on); + continue; + } + } else { + // No data, just loop and check time + usleep(100); // optional: avoid busy-waiting + } + } // End of while loop to discard stale records + } + } // End a while loop to check for the "stop" command + // de-activate the tracee in the nr-softmodem + + // free_database(database); //Do on one app, for example on gNB App + free(is_on); + close(socket); + + return 0; +}