/* * 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 antenna-control-mplane.c * @brief Implementation of O-RAN WG4 M-Plane compliant antenna control * * This implements NETCONF-based antenna muting using the actual O-RAN * YANG models (o-ran-uplane-conf.yang) and the MPLANE-TRX-CONTROL feature. */ #include "antenna-control-mplane.h" #include "ru-mplane-api.h" #include "xml/get-xml.h" #include "common/utils/assertions.h" // Forward declaration - actual function in config-mplane.c extern bool edit_val_commmit_rpc(ru_session_t *ru_session, const char *content); // Forward declaration - actual function in get-mplane.c extern bool get_mplane(ru_session_t *ru_session, char **answer); #include #include #include #include #include const char *antenna_ctrl_result_str(antenna_ctrl_result_t result) { switch (result) { case ANTENNA_CTRL_SUCCESS: return "Success"; case ANTENNA_CTRL_ERR_NO_SESSION: return "No active NETCONF session"; case ANTENNA_CTRL_ERR_FEATURE_NOT_SUPPORTED: return "MPLANE-TRX-CONTROL feature not supported by RU"; case ANTENNA_CTRL_ERR_INVALID_ARRAY_NAME: return "Invalid array name"; case ANTENNA_CTRL_ERR_INVALID_MASK: return "Invalid antenna bitmask"; case ANTENNA_CTRL_ERR_NETCONF_FAILED: return "NETCONF RPC failed"; case ANTENNA_CTRL_ERR_VALIDATION_FAILED: return "Configuration validation failed"; case ANTENNA_CTRL_ERR_COMMIT_FAILED: return "Configuration commit failed"; default: return "Unknown error"; } } /** * @brief Convert antenna bitmask to O-RAN binary format * * O-RAN uses binary type in YANG which is base64 encoded in XML. * For simplicity, we encode the bitmask as hex bytes. * * Per O-RAN spec: "The antenna mask contains as many binary values as * number of array elements associated with respective tx or rx-array. * Value of '1' indicates antenna element shall be kept active and * value of '0' indicates antenna element should be turned inactive." */ static char *bitmask_to_base64(uint8_t bitmask, uint8_t num_antennas) { // For up to 8 antennas, we need 1 byte // Base64 encoding of 1 byte = 4 characters + null terminator char *result = malloc(8); if (result == NULL) return NULL; // Mask to only include configured antennas uint8_t valid_mask = (1 << num_antennas) - 1; uint8_t masked_value = bitmask & valid_mask; // Simple base64 encoding for single byte // Input: masked_value (8 bits) -> padded to 6+6+4 bits for base64 static const char b64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; // Encode single byte with padding result[0] = b64chars[(masked_value >> 2) & 0x3F]; result[1] = b64chars[(masked_value << 4) & 0x30]; result[2] = '='; result[3] = '='; result[4] = '\0'; return result; } /** * @brief Decode base64 to antenna bitmask */ static uint8_t base64_to_bitmask(const char *b64_str) { if (b64_str == NULL || strlen(b64_str) < 2) return 0xFF; static const char b64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; // Find index of first two base64 characters const char *p1 = strchr(b64chars, b64_str[0]); const char *p2 = strchr(b64chars, b64_str[1]); if (p1 == NULL || p2 == NULL) return 0xFF; uint8_t val1 = (uint8_t)(p1 - b64chars); uint8_t val2 = (uint8_t)(p2 - b64chars); // Reconstruct byte return (val1 << 2) | (val2 >> 4); } bool mplane_create_antenna_mask_xml(ru_session_t *ru_session, bool is_tx, const char *array_name, uint8_t antenna_bitmask, uint8_t num_antennas, char **xml_content) { if (ru_session == NULL || ru_session->ctx == NULL || array_name == NULL || xml_content == NULL) { return false; } *xml_content = NULL; LY_ERR ret = LY_SUCCESS; struct lyd_node *root = NULL; // Create root node for o-ran-uplane-conf ret = lyd_new_path(NULL, (struct ly_ctx *)ru_session->ctx, "/o-ran-uplane-conf:user-plane-configuration", NULL, 0, &root); if (ret != LY_SUCCESS) { MP_LOG_W("Failed to create root user-plane-configuration node.\n"); return false; } // Create the antenna mask configuration const char *list_name = is_tx ? "tx-array-antenna-mask-config" : "rx-array-antenna-mask-config"; struct lyd_node *mask_node = NULL; ret = lyd_new_list(root, NULL, list_name, 0, &mask_node, array_name); if (ret != LY_SUCCESS) { MP_LOG_W("Failed to create %s list node for array '%s'.\n", list_name, array_name); lyd_free_tree(root); return false; } // Convert bitmask to base64 for YANG binary type char *b64_mask = bitmask_to_base64(antenna_bitmask, num_antennas); if (b64_mask == NULL) { MP_LOG_W("Failed to encode antenna bitmask.\n"); lyd_free_tree(root); return false; } // Add antenna-bitmask leaf ret = lyd_new_term(mask_node, NULL, "antenna-bitmask", b64_mask, 0, NULL); free(b64_mask); if (ret != LY_SUCCESS) { MP_LOG_W("Failed to create antenna-bitmask node.\n"); lyd_free_tree(root); return false; } // Print to XML // Note: LYD_PRINT_SIBLINGS in libyang v2.1+, LYD_PRINT_WITHSIBLINGS in older v2, LYP_WITHSIBLINGS in v1 #ifdef MPLANE_V1 lyd_print_mem(xml_content, root, LYD_XML, LYP_WITHSIBLINGS); #elif defined MPLANE_V2 #ifdef LYD_PRINT_SIBLINGS lyd_print_mem(xml_content, root, LYD_XML, LYD_PRINT_SIBLINGS); #else lyd_print_mem(xml_content, root, LYD_XML, LYD_PRINT_WITHSIBLINGS); #endif #endif lyd_free_tree(root); if (*xml_content == NULL) { MP_LOG_W("Failed to generate XML content.\n"); return false; } MP_LOG_I("Generated %s antenna mask XML for array '%s', mask=0x%02X\n", is_tx ? "TX" : "RX", array_name, antenna_bitmask); return true; } antenna_ctrl_result_t mplane_set_tx_antenna_mask(ru_session_t *ru_session, const char *array_name, uint8_t antenna_bitmask, uint8_t num_antennas) { if (ru_session == NULL || ru_session->session == NULL) { return ANTENNA_CTRL_ERR_NO_SESSION; } if (array_name == NULL || strlen(array_name) == 0) { return ANTENNA_CTRL_ERR_INVALID_ARRAY_NAME; } if (num_antennas == 0 || num_antennas > 8) { return ANTENNA_CTRL_ERR_INVALID_MASK; } // Generate XML for antenna mask configuration char *xml_content = NULL; if (!mplane_create_antenna_mask_xml(ru_session, true, array_name, antenna_bitmask, num_antennas, &xml_content)) { return ANTENNA_CTRL_ERR_NETCONF_FAILED; } MP_LOG_I("Setting TX antenna mask via NETCONF:\n%s\n", xml_content); // Use existing M-Plane infrastructure to send edit-config, validate, commit bool success = edit_val_commmit_rpc(ru_session, xml_content); free(xml_content); if (!success) { MP_LOG_W("Failed to configure TX antenna mask for array '%s'\n", array_name); return ANTENNA_CTRL_ERR_COMMIT_FAILED; } MP_LOG_I("Successfully configured TX antenna mask 0x%02X for array '%s'\n", antenna_bitmask, array_name); return ANTENNA_CTRL_SUCCESS; } antenna_ctrl_result_t mplane_set_rx_antenna_mask(ru_session_t *ru_session, const char *array_name, uint8_t antenna_bitmask, uint8_t num_antennas) { if (ru_session == NULL || ru_session->session == NULL) { return ANTENNA_CTRL_ERR_NO_SESSION; } if (array_name == NULL || strlen(array_name) == 0) { return ANTENNA_CTRL_ERR_INVALID_ARRAY_NAME; } if (num_antennas == 0 || num_antennas > 8) { return ANTENNA_CTRL_ERR_INVALID_MASK; } // Generate XML for antenna mask configuration char *xml_content = NULL; if (!mplane_create_antenna_mask_xml(ru_session, false, array_name, antenna_bitmask, num_antennas, &xml_content)) { return ANTENNA_CTRL_ERR_NETCONF_FAILED; } MP_LOG_I("Setting RX antenna mask via NETCONF:\n%s\n", xml_content); // Use existing M-Plane infrastructure to send edit-config, validate, commit bool success = edit_val_commmit_rpc(ru_session, xml_content); free(xml_content); if (!success) { MP_LOG_W("Failed to configure RX antenna mask for array '%s'\n", array_name); return ANTENNA_CTRL_ERR_COMMIT_FAILED; } MP_LOG_I("Successfully configured RX antenna mask 0x%02X for array '%s'\n", antenna_bitmask, array_name); return ANTENNA_CTRL_SUCCESS; } antenna_ctrl_result_t mplane_get_tx_antenna_mask(ru_session_t *ru_session, const char *array_name, uint8_t *antenna_bitmask) { if (ru_session == NULL || ru_session->session == NULL) { return ANTENNA_CTRL_ERR_NO_SESSION; } if (array_name == NULL || antenna_bitmask == NULL) { return ANTENNA_CTRL_ERR_INVALID_ARRAY_NAME; } // Get operational datastore char *operational_ds = NULL; if (!get_mplane(ru_session, &operational_ds)) { return ANTENNA_CTRL_ERR_NETCONF_FAILED; } // Build XPath to find the antenna mask for this array char xpath[256]; snprintf(xpath, sizeof(xpath), "tx-array-antenna-mask-config[array-name='%s']/antenna-bitmask", array_name); // Extract the value from XML response char *mask_b64 = get_ru_xml_node(operational_ds, "antenna-bitmask"); free(operational_ds); if (mask_b64 == NULL) { // No mask configured, assume all active *antenna_bitmask = 0xFF; return ANTENNA_CTRL_SUCCESS; } *antenna_bitmask = base64_to_bitmask(mask_b64); free(mask_b64); MP_LOG_I("Retrieved TX antenna mask 0x%02X for array '%s'\n", *antenna_bitmask, array_name); return ANTENNA_CTRL_SUCCESS; } antenna_ctrl_result_t mplane_get_rx_antenna_mask(ru_session_t *ru_session, const char *array_name, uint8_t *antenna_bitmask) { if (ru_session == NULL || ru_session->session == NULL) { return ANTENNA_CTRL_ERR_NO_SESSION; } if (array_name == NULL || antenna_bitmask == NULL) { return ANTENNA_CTRL_ERR_INVALID_ARRAY_NAME; } // Get operational datastore char *operational_ds = NULL; if (!get_mplane(ru_session, &operational_ds)) { return ANTENNA_CTRL_ERR_NETCONF_FAILED; } // Extract the value from XML response (simplified - real impl would use XPath) char *mask_b64 = get_ru_xml_node(operational_ds, "antenna-bitmask"); free(operational_ds); if (mask_b64 == NULL) { // No mask configured, assume all active *antenna_bitmask = 0xFF; return ANTENNA_CTRL_SUCCESS; } *antenna_bitmask = base64_to_bitmask(mask_b64); free(mask_b64); MP_LOG_I("Retrieved RX antenna mask 0x%02X for array '%s'\n", *antenna_bitmask, array_name); return ANTENNA_CTRL_SUCCESS; } bool mplane_supports_trx_control(ru_session_t *ru_session) { if (ru_session == NULL || ru_session->session == NULL) { return false; } // Check if MPLANE-TRX-CONTROL feature is advertised in RU capabilities // This is done by checking the server capabilities from the hello exchange // or by querying the YANG library struct nc_session *session = (struct nc_session *)ru_session->session; const char * const *caps = nc_session_get_cpblts(session); if (caps == NULL) { return false; } // Look for o-ran-uplane-conf module with MPLANE-TRX-CONTROL feature for (int i = 0; caps[i] != NULL; i++) { if (strstr(caps[i], "o-ran-uplane-conf") != NULL) { // Module is supported, assume feature is enabled // A more complete implementation would query module features MP_LOG_I("RU supports o-ran-uplane-conf module (MPLANE-TRX-CONTROL)\n"); return true; } } MP_LOG_W("RU does not advertise o-ran-uplane-conf module\n"); return false; }