From a826af5839cfd8e40b77702bb22019e3ee77b412 Mon Sep 17 00:00:00 2001 From: athanassopoulos <angelo.athanassopoulos@hhi.fraunhofer.de> Date: Thu, 13 Jan 2022 21:54:26 +0100 Subject: [PATCH] SDAP Initial Implementation --- cmake_targets/CMakeLists.txt | 3 +- common/utils/LOG/log.c | 1 + common/utils/LOG/log.h | 1 + common/utils/T/T_messages.txt | 21 + doc/FEATURE_SET.md | 16 + openair2/COMMON/platform_types.h | 9 + openair2/LAYER2/nr_pdcp/nr_pdcp_entity.c | 4 +- openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h | 2 +- openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c | 96 ++-- openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h | 3 + openair2/LAYER2/nr_pdcp/nr_pdcp_ue_manager.c | 4 +- openair2/RRC/NR/rrc_gNB.c | 8 +- openair2/SDAP/nr_sdap/nr_sdap.c | 73 +++ openair2/SDAP/nr_sdap/nr_sdap.h | 59 +++ openair2/SDAP/nr_sdap/nr_sdap_entity.c | 474 +++++++++++++++++++ openair2/SDAP/nr_sdap/nr_sdap_entity.h | 167 +++++++ openair2/SDAP/nr_sdap/nr_sdap_gnb.c | 87 ---- openair2/SDAP/nr_sdap/nr_sdap_gnb.h | 61 --- openair2/SDAP/nr_sdap/sdap_gNB.c | 30 -- openair3/ocp-gtpu/gtp_itf.cpp | 63 ++- 20 files changed, 938 insertions(+), 244 deletions(-) create mode 100644 openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h create mode 100644 openair2/SDAP/nr_sdap/nr_sdap.c create mode 100644 openair2/SDAP/nr_sdap/nr_sdap.h create mode 100644 openair2/SDAP/nr_sdap/nr_sdap_entity.c create mode 100644 openair2/SDAP/nr_sdap/nr_sdap_entity.h delete mode 100644 openair2/SDAP/nr_sdap/nr_sdap_gnb.c delete mode 100644 openair2/SDAP/nr_sdap/nr_sdap_gnb.h delete mode 100644 openair2/SDAP/nr_sdap/sdap_gNB.c diff --git a/cmake_targets/CMakeLists.txt b/cmake_targets/CMakeLists.txt index 46e33e88033..06ba14965c4 100644 --- a/cmake_targets/CMakeLists.txt +++ b/cmake_targets/CMakeLists.txt @@ -1779,7 +1779,8 @@ set(NR_PDCP_SRC ) set(NR_SDAP_SRC - ${OPENAIR2_DIR}/SDAP/nr_sdap/nr_sdap_gnb.c + ${OPENAIR2_DIR}/SDAP/nr_sdap/nr_sdap.c + ${OPENAIR2_DIR}/SDAP/nr_sdap/nr_sdap_entity.c ) set(L2_SRC diff --git a/common/utils/LOG/log.c b/common/utils/LOG/log.c index 5b047c082d0..8be35680342 100644 --- a/common/utils/LOG/log.c +++ b/common/utils/LOG/log.c @@ -478,6 +478,7 @@ int logInit (void) register_log_component("NAS","log",NAS); register_log_component("UDP","",UDP_); register_log_component("GTPU","",GTPU); + register_log_component("SDAP","",SDAP); register_log_component("S1AP","",S1AP); register_log_component("F1AP","",F1AP); register_log_component("M2AP","",M2AP); diff --git a/common/utils/LOG/log.h b/common/utils/LOG/log.h index 1fbf42f62d4..89b9c5e94d2 100644 --- a/common/utils/LOG/log.h +++ b/common/utils/LOG/log.h @@ -215,6 +215,7 @@ typedef enum { OCM, UDP_, GTPU, + SDAP, SPGW, S1AP, F1AP, diff --git a/common/utils/T/T_messages.txt b/common/utils/T/T_messages.txt index a8b919e5252..b3c06d6be8a 100644 --- a/common/utils/T/T_messages.txt +++ b/common/utils/T/T_messages.txt @@ -944,6 +944,27 @@ ID = LEGACY_GTPU_TRACE GROUP = ALL:LEGACY_GTPU:LEGACY_GROUP_TRACE:LEGACY FORMAT = string,log +ID = LEGACY_SDAP_INFO + DESC = SDAP legacy logs - info level + GROUP = ALL:LEGACY_SDAP:LEGACY_GROUP_INFO:LEGACY + FORMAT = string,log +ID = LEGACY_SDAP_ERROR + DESC = SDAP legacy logs - error level + GROUP = ALL:LEGACY_SDAP:LEGACY_GROUP_ERROR:LEGACY + FORMAT = string,log +ID = LEGACY_SDAP_WARNING + DESC = SDAP legacy logs - warning level + GROUP = ALL:LEGACY_SDAP:LEGACY_GROUP_WARNING:LEGACY + FORMAT = string,log +ID = LEGACY_SDAP_DEBUG + DESC = SDAP legacy logs - debug level + GROUP = ALL:LEGACY_SDAP:LEGACY_GROUP_DEBUG:LEGACY + FORMAT = string,log +ID = LEGACY_SDAP_TRACE + DESC = SDAP legacy logs - trace level + GROUP = ALL:LEGACY_SDAP:LEGACY_GROUP_TRACE:LEGACY + FORMAT = string,log + ID = LEGACY_TMR_INFO DESC = TMR legacy logs - info level GROUP = ALL:LEGACY_TMR:LEGACY_GROUP_INFO:LEGACY diff --git a/doc/FEATURE_SET.md b/doc/FEATURE_SET.md index eaa691d6ef0..a289c6d17eb 100644 --- a/doc/FEATURE_SET.md +++ b/doc/FEATURE_SET.md @@ -351,6 +351,14 @@ The following features are valid for the gNB and the 5G-NR UE. - Interfaces with RRC, RLC - Interfaces with gtp-u (data Tx/Rx over N3 and F1-U interfaces) +**gNB SDAP** +- Send/Receive operations according to 37.324 Rel.15 + - Establishment/Handling of SDAP entities. + - Transfer of User Plane Data + - Mapping between a QoS flow and a DRB for both DL and UL + - Marking QoS flow ID in both DL and UL packets + - Reflective QoS flow to DRB mapping for UL SDAP data PDUs + **gNB RRC** - NR RRC (38.331) Rel 16 messages using new asn1c - LTE RRC (36.331) also updated to Rel 15 @@ -400,6 +408,7 @@ The following features are valid for the gNB and the 5G-NR UE. - New gtp-u implementation supporting both N3 and F1-U interfaces according to 29.281 Rel.15 - Interfaces with RRC, F1AP for tunnel creation - Interfaces with PDCP and RLC for data send/receive at the CU and DU respectively (F1-U interface) + - Interface with SDAP for data send/receive, capture of GTP-U Optional Header, GTP-U Extension Header and PDU Session Container. # OpenAirInterface 5G-NR UE Feature Set # @@ -502,6 +511,13 @@ The following features are valid for the gNB and the 5G-NR UE. - Radio bearer establishment/handling and association with PDCP entities - Interfaces with RRC, RLC +**UE SDAP** +* Tx/Rx operations operations according to 37.324 Rel.15 + - Establishment/Handling of SDAP entities. + - Transfer of User Plane Data + - Reflective Mapping + - RRC Signaling Mapping + **UE RRC** * Integration of RRC messages and procedures supporting UE 5G SA connection according to 38.331 Rel.16 - RRCSetupRequest/RRCSetup/RRCSetupComplete diff --git a/openair2/COMMON/platform_types.h b/openair2/COMMON/platform_types.h index c24291de645..b9a7375b9f2 100644 --- a/openair2/COMMON/platform_types.h +++ b/openair2/COMMON/platform_types.h @@ -233,11 +233,20 @@ typedef uint8_t pdusessionid_t; //----------------------------------------------------------------------------- // may be ITTI not enabled, but type instance is useful also for OTG, typedef intptr_t instance_t; + +typedef struct sdap_protocol_ctxt_s { + boolean_t rqi; + uint8_t qfi; + boolean_t dc; + int pdusession_id; +} sdap_protocol_ctxt_t; + typedef struct protocol_ctxt_s { module_id_t module_id; /*!< \brief Virtualized module identifier */ eNB_flag_t enb_flag; /*!< \brief Flag to indicate eNB (1) or UE (0) */ instance_t instance; /*!< \brief ITTI or OTG module identifier */ rnti_t rnti; + sdap_protocol_ctxt_t sdap; frame_t frame; /*!< \brief LTE frame number.*/ sub_frame_t subframe; /*!< \brief LTE sub frame number.*/ eNB_index_t eNB_index; /*!< \brief valid for UE indicating the index of connected eNB(s) */ diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.c b/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.c index 62aa101a500..42b9f171cea 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.c +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.c @@ -368,8 +368,8 @@ nr_pdcp_entity_t *new_nr_pdcp_entity( ret->set_security = nr_pdcp_entity_set_security; ret->set_time = nr_pdcp_entity_set_time; - ret->delete = nr_pdcp_entity_delete; - + ret->delete_entity = nr_pdcp_entity_delete; + ret->deliver_sdu = deliver_sdu; ret->deliver_sdu_data = deliver_sdu_data; diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h b/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h index fbcc2b96cd3..49e9c4142b5 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h @@ -39,7 +39,7 @@ typedef struct nr_pdcp_entity_t { void (*recv_pdu)(struct nr_pdcp_entity_t *entity, char *buffer, int size); void (*recv_sdu)(struct nr_pdcp_entity_t *entity, char *buffer, int size, int sdu_id); - void (*delete)(struct nr_pdcp_entity_t *entity); + void (*delete_entity)(struct nr_pdcp_entity_t *entity); /* set_security: pass -1 to integrity_algorithm / ciphering_algorithm * to keep the current algorithm diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c index 7d1e2f49318..3c999161698 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c @@ -36,7 +36,8 @@ #include "pdcp.h" #include "LAYER2/nr_rlc/nr_rlc_oai_api.h" #include <openair3/ocp-gtpu/gtp_itf.h> -#include "openair2/SDAP/nr_sdap/nr_sdap_gnb.h" +#include "openair2/SDAP/nr_sdap/nr_sdap.h" +#include "nr_pdcp_oai_api.h" #define TODO do { \ printf("%s:%d:%s: todo\n", __FILE__, __LINE__, __FUNCTION__); \ @@ -438,9 +439,13 @@ static void *enb_tun_read_thread(void *_) ctxt.rnti = rnti; - pdcp_data_req(&ctxt, SRB_FLAG_NO, rb_id, RLC_MUI_UNDEFINED, - RLC_SDU_CONFIRM_NO, len, (unsigned char *)rx_buf, - PDCP_TRANSMISSION_MODE_DATA, NULL, NULL); + ctxt.sdap.qfi = 7; + ctxt.sdap.rqi = 0; + ctxt.sdap.pdusession_id = 10; + + sdap_data_req(&ctxt, SRB_FLAG_NO, rb_id, RLC_MUI_UNDEFINED, + RLC_SDU_CONFIRM_NO, len, (unsigned char *)rx_buf, + PDCP_TRANSMISSION_MODE_DATA, NULL, NULL); } return NULL; @@ -481,9 +486,13 @@ static void *ue_tun_read_thread(void *_) ctxt.rnti = rnti; - pdcp_data_req(&ctxt, SRB_FLAG_NO, rb_id, RLC_MUI_UNDEFINED, - RLC_SDU_CONFIRM_NO, len, (unsigned char *)rx_buf, - PDCP_TRANSMISSION_MODE_DATA, NULL, NULL); + ctxt.sdap.dc = SDAP_HDR_UL_DATA_PDU; + ctxt.sdap.qfi = 7; + ctxt.sdap.pdusession_id = 10; + + sdap_data_req(&ctxt, SRB_FLAG_NO, rb_id, RLC_MUI_UNDEFINED, + RLC_SDU_CONFIRM_NO, len, (unsigned char *)rx_buf, + PDCP_TRANSMISSION_MODE_DATA, NULL, NULL); } return NULL; @@ -607,28 +616,13 @@ uint64_t nr_pdcp_module_init(uint64_t _pdcp_optmask, int id) static void deliver_sdu_drb(void *_ue, nr_pdcp_entity_t *entity, char *buf, int size) { - extern int nas_sock_fd[]; - int len; nr_pdcp_ue_t *ue = _ue; - MessageDef *message_p; - uint8_t *gtpu_buffer_p; int rb_id; int i; if (IS_SOFTMODEM_NOS1 || UE_NAS_USE_TUN) { - LOG_D(PDCP, "IP packet received, to be sent to TUN interface"); - - if(entity->has_sdapDLheader){ - size -= SDAP_HDR_LENGTH; - len = write(nas_sock_fd[0], &buf[SDAP_HDR_LENGTH], size); - } else { - len = write(nas_sock_fd[0], buf, size); - } - - if (len != size) { - LOG_E(PDCP, "%s:%d:%s: fatal error %d: %s\n", __FILE__, __LINE__, __FUNCTION__, errno, strerror(errno)); - } - + LOG_D(PDCP, "IP packet received with size %d, to be sent to SDAP interface, UE rnti: %d\n", size, ue->rnti); + sdap_data_ind(entity, ue->rnti, buf, size); } else{ for (i = 0; i < 5; i++) { @@ -644,31 +638,9 @@ static void deliver_sdu_drb(void *_ue, nr_pdcp_entity_t *entity, rb_found: { - int offset=0; - if (entity->has_sdap == 1 && entity->has_sdapULheader == 1) - offset = 1; // this is the offset of the SDAP header in bytes - - message_p = itti_alloc_new_message_sized(TASK_PDCP_ENB, 0, - GTPV1U_GNB_TUNNEL_DATA_REQ, - sizeof(gtpv1u_gnb_tunnel_data_req_t) + size - + GTPU_HEADER_OVERHEAD_MAX - offset); - AssertFatal(message_p != NULL, "OUT OF MEMORY"); - - gtpv1u_gnb_tunnel_data_req_t *req=>PV1U_GNB_TUNNEL_DATA_REQ(message_p); - gtpu_buffer_p = (uint8_t*)(req+1); - memcpy(gtpu_buffer_p+GTPU_HEADER_OVERHEAD_MAX, buf+offset, size-offset); - req->buffer = gtpu_buffer_p; - req->length = size-offset; - req->offset = GTPU_HEADER_OVERHEAD_MAX; - req->rnti = ue->rnti; - req->pdusession_id = entity->pdusession_id; - if (offset==1) { - LOG_I(PDCP, "%s() (drb %d) SDAP header %2x\n",__func__, rb_id, buf[0]); - sdap_gnb_ul_header_handler(buf[0]); // Handler for the UL gNB SDAP Header - } - LOG_D(PDCP, "%s() (drb %d) sending message to gtp size %d\n", __func__, rb_id, size-offset); - itti_send_msg_to_task(TASK_GTPV1_U, INSTANCE_DEFAULT, message_p); - } + LOG_D(PDCP, "%s() (drb %d) sending message to SDAP size %d\n", __func__, rb_id, size); + sdap_data_ind(entity, ue->rnti, buf, size); + } } } @@ -967,6 +939,9 @@ static void add_drb_am(int is_gnb, int rnti, struct NR_DRB_ToAddMod *s, int has_sdap = 0; int has_sdapULheader=0; int has_sdapDLheader=0; + boolean_t is_sdap_DefaultDRB = false; + NR_QFI_t *mappedQFIs2Add = NULL; + uint8_t mappedQFIs2AddCount=0; if (s->cnAssociation->present == NR_DRB_ToAddMod__cnAssociation_PR_eps_BearerIdentity) pdusession_id = s->cnAssociation->choice.eps_BearerIdentity; else { @@ -978,6 +953,10 @@ static void add_drb_am(int is_gnb, int rnti, struct NR_DRB_ToAddMod *s, has_sdap = 1; has_sdapULheader = s->cnAssociation->choice.sdap_Config->sdap_HeaderUL == NR_SDAP_Config__sdap_HeaderUL_present ? 1 : 0; has_sdapDLheader = s->cnAssociation->choice.sdap_Config->sdap_HeaderDL == NR_SDAP_Config__sdap_HeaderDL_present ? 1 : 0; + is_sdap_DefaultDRB = s->cnAssociation->choice.sdap_Config->defaultDRB == true ? 1 : 0; + mappedQFIs2Add = (NR_QFI_t*)s->cnAssociation->choice.sdap_Config->mappedQoS_FlowsToAdd->list.array[0]; + mappedQFIs2AddCount = s->cnAssociation->choice.sdap_Config->mappedQoS_FlowsToAdd->list.count; + LOG_D(SDAP, "Captured mappedQoS_FlowsToAdd from RRC: %ld \n", *mappedQFIs2Add); } /* TODO(?): accept different UL and DL SN sizes? */ if (sn_size_ul != sn_size_dl) { @@ -1009,6 +988,14 @@ static void add_drb_am(int is_gnb, int rnti, struct NR_DRB_ToAddMod *s, nr_pdcp_ue_add_drb_pdcp_entity(ue, drb_id, pdcp_drb); LOG_D(PDCP, "%s:%d:%s: added drb %d to ue rnti %x\n", __FILE__, __LINE__, __FUNCTION__, drb_id, rnti); + + new_nr_sdap_entity(rnti, + pdusession_id, + is_sdap_DefaultDRB, + drb_id, + mappedQFIs2Add, + mappedQFIs2AddCount); + LOG_D(SDAP, "Added SDAP entity to ue rnti %x with pdusession_id %d\n", rnti, pdusession_id); } nr_pdcp_manager_unlock(nr_pdcp_ue_manager); } @@ -1140,7 +1127,7 @@ void nr_DRB_preconfiguration(uint16_t crnti) NR_DRB_ToAddMod_t *drb_ToAddMod = calloc(1,sizeof(*drb_ToAddMod)); drb_ToAddMod->cnAssociation = calloc(1,sizeof(*drb_ToAddMod->cnAssociation)); drb_ToAddMod->cnAssociation->present = NR_DRB_ToAddMod__cnAssociation_PR_eps_BearerIdentity; - drb_ToAddMod->cnAssociation->choice.eps_BearerIdentity= 5; + drb_ToAddMod->cnAssociation->choice.eps_BearerIdentity= 10; drb_ToAddMod->drb_Identity = 1; drb_ToAddMod->reestablishPDCP = NULL; drb_ToAddMod->recoverPDCP = NULL; @@ -1450,3 +1437,12 @@ void nr_pdcp_tick(int frame, int subframe) nr_pdcp_wakeup_timer_thread(nr_pdcp_current_time); } } + +nr_pdcp_entity_t *nr_pdcp_find_entity_sdap(int rnti, uint8_t sdap_drb_id){ + nr_pdcp_ue_t *ue; + ue = nr_pdcp_manager_get_ue(nr_pdcp_ue_manager, rnti); + if(ue->drb[sdap_drb_id-1]) + return ue->drb[sdap_drb_id-1]; + + return NULL; +} diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h new file mode 100644 index 00000000000..59e366ca46b --- /dev/null +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h @@ -0,0 +1,3 @@ +#include "openair2/COMMON/platform_types.h" + +nr_pdcp_entity_t *nr_pdcp_find_entity_sdap(int rnti, uint8_t sdap_drb_id); diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_ue_manager.c b/openair2/LAYER2/nr_pdcp/nr_pdcp_ue_manager.c index 160c5ebb226..1f21593c630 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_ue_manager.c +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_ue_manager.c @@ -127,11 +127,11 @@ void nr_pdcp_manager_remove_ue(nr_pdcp_ue_manager_t *_m, int rnti) for (j = 0; j < 2; j++) if (ue->srb[j] != NULL) - ue->srb[j]->delete(ue->srb[j]); + ue->srb[j]->delete_entity(ue->srb[j]); for (j = 0; j < 5; j++) if (ue->drb[j] != NULL) - ue->drb[j]->delete(ue->drb[j]); + ue->drb[j]->delete_entity(ue->drb[j]); free(ue); diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 86b767f83cd..5ea118f64ec 100755 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -818,7 +818,6 @@ rrc_gNB_generate_dedicatedRRCReconfiguration( uint8_t buffer[RRC_BUF_SIZE]; uint16_t size; int qos_flow_index = 0; - NR_QFI_t qfi = 0; int pdu_sessions_done = 0; int i; NR_CellGroupConfig_t *cellGroupConfig; @@ -870,14 +869,15 @@ rrc_gNB_generate_dedicatedRRCReconfiguration( memset(sdap_config, 0, sizeof(NR_SDAP_Config_t)); sdap_config->pdu_Session = ue_context_pP->ue_context.pduSession[i].param.pdusession_id; sdap_config->sdap_HeaderDL = NR_SDAP_Config__sdap_HeaderDL_present; - sdap_config->sdap_HeaderUL = NR_SDAP_Config__sdap_HeaderUL_absent; + sdap_config->sdap_HeaderUL = NR_SDAP_Config__sdap_HeaderUL_present; sdap_config->defaultDRB = TRUE; sdap_config->mappedQoS_FlowsToAdd = calloc(1, sizeof(struct NR_SDAP_Config__mappedQoS_FlowsToAdd)); memset(sdap_config->mappedQoS_FlowsToAdd, 0, sizeof(struct NR_SDAP_Config__mappedQoS_FlowsToAdd)); for (qos_flow_index = 0; qos_flow_index < ue_context_pP->ue_context.pduSession[i].param.nb_qos; qos_flow_index++) { - qfi = ue_context_pP->ue_context.pduSession[i].param.qos[qos_flow_index].qfi; - ASN_SEQUENCE_ADD(&sdap_config->mappedQoS_FlowsToAdd->list, &qfi); + NR_QFI_t *qfi = calloc(1, sizeof(NR_QFI_t)); + *qfi = ue_context_pP->ue_context.pduSession[i].param.qos[qos_flow_index].qfi; + ASN_SEQUENCE_ADD(&sdap_config->mappedQoS_FlowsToAdd->list, qfi); } sdap_config->mappedQoS_FlowsToRelease = NULL; DRB_config->cnAssociation->choice.sdap_Config = sdap_config; diff --git a/openair2/SDAP/nr_sdap/nr_sdap.c b/openair2/SDAP/nr_sdap/nr_sdap.c new file mode 100644 index 00000000000..674dd700b1d --- /dev/null +++ b/openair2/SDAP/nr_sdap/nr_sdap.c @@ -0,0 +1,73 @@ +/* + * 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 + */ + +#include "nr_sdap.h" + +boolean_t sdap_data_req(protocol_ctxt_t *ctxt_p, + const srb_flag_t srb_flag, + const rb_id_t rb_id, + const mui_t mui, + const confirm_t confirm, + const sdu_size_t sdu_buffer_size, + unsigned char *const sdu_buffer, + const pdcp_transmission_mode_t pt_mode, + const uint32_t *sourceL2Id, + const uint32_t *destinationL2Id) { + nr_sdap_entity_t *sdap_entity; + sdap_entity = nr_sdap_get_entity(ctxt_p->rnti, ctxt_p->sdap.pdusession_id); + + if(sdap_entity == NULL) { + LOG_E(SDAP, "%s:%d:%s: Entity not found with ue rnti: %x and pdusession id: %d\n", __FILE__, __LINE__, __FUNCTION__, ctxt_p->rnti, ctxt_p->sdap.pdusession_id); + return 0; + } + + boolean_t ret = sdap_entity->tx_entity(sdap_entity, + ctxt_p, + srb_flag, + rb_id, + mui, + confirm, + sdu_buffer_size, + sdu_buffer, + pt_mode, + sourceL2Id, + destinationL2Id); + return ret; +} + +void sdap_data_ind(nr_pdcp_entity_t *pdcp_entity, + int rnti, + char *buf, + int size) { + nr_sdap_entity_t *sdap_entity; + sdap_entity = nr_sdap_get_entity(rnti, pdcp_entity->pdusession_id); + + if(sdap_entity == NULL) { + LOG_E(SDAP, "%s:%d:%s: Entity not found\n", __FILE__, __LINE__, __FUNCTION__); + return; + } + + sdap_entity->rx_entity(sdap_entity, + pdcp_entity, + rnti, + buf, + size); +} diff --git a/openair2/SDAP/nr_sdap/nr_sdap.h b/openair2/SDAP/nr_sdap/nr_sdap.h new file mode 100644 index 00000000000..e4e8004e2d8 --- /dev/null +++ b/openair2/SDAP/nr_sdap/nr_sdap.h @@ -0,0 +1,59 @@ +/* + * 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 + */ + +#ifndef _NR_SDAP_GNB_H_ +#define _NR_SDAP_GNB_H_ + +#include "openair2/COMMON/platform_types.h" +#include "common/utils/LOG/log.h" +#include "nr_sdap_entity.h" + +/* + * TS 37.324 4.4 Functions + * Transfer of user plane data + * Downlink - gNB + * Uplink - nrUE + */ +boolean_t sdap_data_req(protocol_ctxt_t *ctxt_p, + const srb_flag_t srb_flag, + const rb_id_t rb_id, + const mui_t mui, + const confirm_t confirm, + const sdu_size_t sdu_buffer_size, + unsigned char *const sdu_buffer, + const pdcp_transmission_mode_t pt_mode, + const uint32_t *sourceL2Id, + const uint32_t *destinationL2Id + ); + +/* + * TS 37.324 4.4 Functions + * Transfer of user plane data + * Uplink - gNB + * Downlink - nrUE + */ +void sdap_data_ind(nr_pdcp_entity_t *pdcp_entity, + int rnti, + char *buf, + int size + ); + +#endif diff --git a/openair2/SDAP/nr_sdap/nr_sdap_entity.c b/openair2/SDAP/nr_sdap/nr_sdap_entity.c new file mode 100644 index 00000000000..028653372fd --- /dev/null +++ b/openair2/SDAP/nr_sdap/nr_sdap_entity.c @@ -0,0 +1,474 @@ +/* + * 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 + */ + +#include "nr_sdap_entity.h" +#include "common/utils/LOG/log.h" +#include <openair2/LAYER2/PDCP_v10.1.0/pdcp.h> +#include <openair3/ocp-gtpu/gtp_itf.h> +#include "openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h" + +#include <stdlib.h> +#include <string.h> +#include <pthread.h> + +typedef struct { + nr_sdap_entity_t *sdap_entity_llist; +} nr_sdap_entity_info; + +static nr_sdap_entity_info sdap_info; + +static boolean_t nr_sdap_tx_entity(nr_sdap_entity_t *entity, + protocol_ctxt_t *ctxt_p, + const srb_flag_t srb_flag, + const rb_id_t rb_id, + const mui_t mui, + const confirm_t confirm, + const sdu_size_t sdu_buffer_size, + unsigned char *const sdu_buffer, + const pdcp_transmission_mode_t pt_mode, + const uint32_t *sourceL2Id, + const uint32_t *destinationL2Id + ) { + /* The offset of the SDAP header, it might be 0 if the has_sdap is not true in the pdcp entity. */ + int offset=0; + boolean_t ret=false; + /*Hardcode DRB ID given from upper layer (ue/enb_tun_read_thread rb_id), it will change if we have SDAP*/ + rb_id_t sdap_drb_id = rb_id; + int pdcp_ent_has_sdap = 0; + + if(sdu_buffer == NULL) { + LOG_E(SDAP, "%s:%d:%s: NULL sdu_buffer \n", __FILE__, __LINE__, __FUNCTION__); + exit(1); + } + + uint8_t sdap_buf[SDAP_MAX_PDU]; + nr_pdcp_entity_t *pdcp_entity = entity->qfi2drb_map(entity, ctxt_p->sdap.qfi, rb_id); + + if(pdcp_entity){ + sdap_drb_id = pdcp_entity->rb_id; + pdcp_ent_has_sdap = pdcp_entity->has_sdap; + } + + if(!pdcp_ent_has_sdap){ + ret = pdcp_data_req(ctxt_p, + srb_flag, + sdap_drb_id, + mui, + confirm, + sdu_buffer_size, + sdu_buffer, + pt_mode, + sourceL2Id, + destinationL2Id); + + if(!ret) + LOG_E(SDAP, "%s:%d:%s: PDCP refused PDU\n", __FILE__, __LINE__, __FUNCTION__); + + return ret; + } + + if(sdu_buffer_size == 0 || sdu_buffer_size > 8999) { + LOG_E(SDAP, "%s:%d:%s: NULL or 0 or exceeded sdu_buffer_size (over max PDCP SDU)\n", __FILE__, __LINE__, __FUNCTION__); + return 0; + } + + if(ctxt_p->enb_flag) { // gNB + offset = SDAP_HDR_LENGTH; + /* + * TS 37.324 4.4 Functions + * marking QoS flow ID in DL packets. + * + * Construct the DL SDAP data PDU. + */ + nr_sdap_dl_hdr_t sdap_hdr; + sdap_hdr.QFI = ctxt_p->sdap.qfi; + sdap_hdr.RQI = ctxt_p->sdap.rqi; + sdap_hdr.RDI = 0; // SDAP Hardcoded Value + /* Add the SDAP DL Header to the buffer */ + memcpy(&sdap_buf[0], &sdap_hdr, SDAP_HDR_LENGTH); + memcpy(&sdap_buf[SDAP_HDR_LENGTH], sdu_buffer, sdu_buffer_size); + LOG_D(SDAP, "TX Entity QFI: %u \n", sdap_hdr.QFI); + LOG_D(SDAP, "TX Entity RQI: %u \n", sdap_hdr.RQI); + LOG_D(SDAP, "TX Entity RDI: %u \n", sdap_hdr.RDI); + } else { // nrUE + offset = SDAP_HDR_LENGTH; + /* + * TS 37.324 4.4 Functions + * marking QoS flow ID in UL packets. + * + * 5.2.1 Uplink + * construct the UL SDAP data PDU as specified in the subclause 6.2.2.3. + */ + nr_sdap_ul_hdr_t sdap_hdr; + sdap_hdr.QFI = ctxt_p->sdap.qfi; + sdap_hdr.R = 0; + sdap_hdr.DC = ctxt_p->sdap.dc; + /* Add the SDAP UL Header to the buffer */ + memcpy(&sdap_buf[0], &sdap_hdr, SDAP_HDR_LENGTH); + memcpy(&sdap_buf[SDAP_HDR_LENGTH], sdu_buffer, sdu_buffer_size); + LOG_D(SDAP, "TX Entity QFI: %u \n", sdap_hdr.QFI); + LOG_D(SDAP, "TX Entity R: %u \n", sdap_hdr.R); + LOG_D(SDAP, "TX Entity DC: %u \n", sdap_hdr.DC); + } + + /* + * TS 37.324 5.2 Data transfer + * 5.2.1 Uplink UE side + * submit the constructed UL SDAP data PDU to the lower layers + * + * Downlink gNB side + */ + ret = pdcp_data_req(ctxt_p, + srb_flag, + sdap_drb_id, + mui, + confirm, + sdu_buffer_size+offset, + sdap_buf, + pt_mode, + sourceL2Id, + destinationL2Id); + + if(!ret) + LOG_E(SDAP, "%s:%d:%s: PDCP refused PDU\n", __FILE__, __LINE__, __FUNCTION__); + + return ret; +} + +static void nr_sdap_rx_entity(nr_sdap_entity_t *entity, + nr_pdcp_entity_t *pdcp_entity, + int rnti, + char *buf, + int size) { + /* The offset of the SDAP header, it might be 0 if the has_sdap is not true in the pdcp entity. */ + int offset=0; + + if(pdcp_entity->is_gnb) { // gNB + if(pdcp_entity->has_sdap && pdcp_entity->has_sdapULheader ) { // Handling the SDAP Header + offset = SDAP_HDR_LENGTH; + nr_sdap_ul_hdr_t *sdap_hdr = (nr_sdap_ul_hdr_t *)buf; + LOG_D(SDAP, "RX Entity Received QFI : %u\n", sdap_hdr->QFI); + LOG_D(SDAP, "RX Entity Received Reserved bit : %u\n", sdap_hdr->R); + LOG_D(SDAP, "RX Entity Received DC bit : %u\n", sdap_hdr->DC); + + switch (sdap_hdr->DC) { + case SDAP_HDR_UL_DATA_PDU: + LOG_D(SDAP, "RX Entity Received SDAP Data PDU\n"); + break; + + case SDAP_HDR_UL_CTRL_PDU: + LOG_D(SDAP, "RX Entity Received SDAP Control PDU\n"); + break; + } + } + + // Pushing SDAP SDU to GTP-U Layer + MessageDef *message_p; + uint8_t *gtpu_buffer_p; + gtpu_buffer_p = itti_malloc(TASK_PDCP_ENB, TASK_GTPV1_U, size + GTPU_HEADER_OVERHEAD_MAX - offset); + AssertFatal(gtpu_buffer_p != NULL, "OUT OF MEMORY"); + memcpy(>pu_buffer_p[GTPU_HEADER_OVERHEAD_MAX], buf+offset, size-offset); + message_p = itti_alloc_new_message(TASK_PDCP_ENB, 0 , GTPV1U_GNB_TUNNEL_DATA_REQ); + AssertFatal(message_p != NULL, "OUT OF MEMORY"); + GTPV1U_GNB_TUNNEL_DATA_REQ(message_p).buffer = gtpu_buffer_p; + GTPV1U_GNB_TUNNEL_DATA_REQ(message_p).length = size-offset; + GTPV1U_GNB_TUNNEL_DATA_REQ(message_p).offset = GTPU_HEADER_OVERHEAD_MAX; + GTPV1U_GNB_TUNNEL_DATA_REQ(message_p).rnti = rnti; + GTPV1U_GNB_TUNNEL_DATA_REQ(message_p).pdusession_id = pdcp_entity->pdusession_id; + LOG_D(SDAP, "%s() sending message to gtp size %d\n", __func__, size-offset); + itti_send_msg_to_task(TASK_VARIABLE, INSTANCE_DEFAULT, message_p); + } else { //nrUE + /* + * TS 37.324 5.2 Data transfer + * 5.2.2 Downlink + * if the DRB from which this SDAP data PDU is received is configured by RRC with the presence of SDAP header. + */ + if(pdcp_entity->has_sdap && pdcp_entity->has_sdapDLheader) { // Handling the SDAP Header + offset = SDAP_HDR_LENGTH; + /* + * TS 37.324 5.2 Data transfer + * 5.2.2 Downlink + * retrieve the SDAP SDU from the DL SDAP data PDU as specified in the subclause 6.2.2.2. + */ + nr_sdap_dl_hdr_t *sdap_hdr = (nr_sdap_dl_hdr_t *)buf; + LOG_D(SDAP, "RX Entity Received QFI : %u\n", sdap_hdr->QFI); + LOG_D(SDAP, "RX Entity Received RQI : %u\n", sdap_hdr->RQI); + LOG_D(SDAP, "RX Entity Received RDI : %u\n", sdap_hdr->RDI); + + /* + * TS 37.324 5.2 Data transfer + * 5.2.2 Downlink + * Perform reflective QoS flow to DRB mapping as specified in the subclause 5.3.2. + */ + if(sdap_hdr->RDI == SDAP_REFLECTIVE_MAPPING) { + /* + * TS 37.324 5.3 QoS flow to DRB Mapping + * 5.3.2 Reflective mapping + * If there is no stored QoS flow to DRB mapping rule for the QoS flow and a default DRB is configured. + */ + if(!entity->qfi2drb_table[sdap_hdr->QFI] && entity->default_drb->rb_id){ + nr_sdap_ul_hdr_t sdap_ctrl_pdu = entity->sdap_construct_ctrl_pdu(sdap_hdr->QFI); + nr_pdcp_entity_t *sdap_ctrl_pdu_drb = entity->sdap_map_ctrl_pdu(entity, pdcp_entity, SDAP_CTRL_PDU_MAP_DEF_DRB, sdap_hdr->QFI); + entity->sdap_submit_ctrl_pdu(rnti, sdap_ctrl_pdu_drb, sdap_ctrl_pdu); + } + + /* + * TS 37.324 5.3 QoS flow to DRB mapping + * 5.3.2 Reflective mapping + * if the stored QoS flow to DRB mapping rule for the QoS flow + * is different from the QoS flow to DRB mapping of the DL SDAP data PDU + * and + * the DRB according to the stored QoS flow to DRB mapping rule is configured by RRC + * with the presence of UL SDAP header + */ + if( (pdcp_entity->rb_id != entity->qfi2drb_table[sdap_hdr->QFI]->rb_id) && + pdcp_entity->has_sdapULheader ){ + nr_sdap_ul_hdr_t sdap_ctrl_pdu = entity->sdap_construct_ctrl_pdu(sdap_hdr->QFI); + nr_pdcp_entity_t *sdap_ctrl_pdu_drb = entity->sdap_map_ctrl_pdu(entity, pdcp_entity, SDAP_CTRL_PDU_MAP_RULE_DRB, sdap_hdr->QFI); + entity->sdap_submit_ctrl_pdu(rnti, sdap_ctrl_pdu_drb, sdap_ctrl_pdu); + } + + /* + * TS 37.324 5.3 QoS flow to DRB Mapping + * 5.3.2 Reflective mapping + * store the QoS flow to DRB mapping of the DL SDAP data PDU as the QoS flow to DRB mapping rule for the UL. + */ + entity->qfi2drb_table[sdap_hdr->QFI]->rb_id = pdcp_entity->rb_id; + } + + /* + * TS 37.324 5.2 Data transfer + * 5.2.2 Downlink + * perform RQI handling as specified in the subclause 5.4 + */ + if(sdap_hdr->RQI == SDAP_RQI_HANDLING) { + LOG_W(SDAP, "UE - TODD 5.4\n"); + } + } /* else - retrieve the SDAP SDU from the DL SDAP data PDU as specified in the subclause 6.2.2.1 */ + + /* + * TS 37.324 5.2 Data transfer + * 5.2.2 Downlink + * deliver the retrieved SDAP SDU to the upper layer. + */ + extern int nas_sock_fd[]; + int len = write(nas_sock_fd[0], &buf[offset], size-offset); + LOG_D(SDAP, "RX Entity len : %d\n", len); + LOG_D(SDAP, "RX Entity size : %d\n", size); + LOG_D(SDAP, "RX Entity offset : %d\n", offset); + + if (len != size-offset) + LOG_E(SDAP, "%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__); + } +} + +void nr_sdap_qfi2drb_map_update(nr_sdap_entity_t *entity, uint8_t qfi, uint8_t drb){ + if(qfi < SDAP_MAX_QFI && + qfi > SDAP_MAP_RULE_EMPTY && + drb > 0 && + drb <= AVLBL_DRB) + { + nr_pdcp_entity_t *pdcp_entity; + pdcp_entity = nr_pdcp_find_entity_sdap(entity->rnti, drb); + + if(!pdcp_entity){ + LOG_D(SDAP, "Map Update failed - PDCP Entity does not exist, should create it\n"); + LOG_D(SDAP, "Map Update failed - Mapping to default DRB\n"); + entity->qfi2drb_table[qfi] = entity->default_drb; + } + + entity->qfi2drb_table[qfi] = pdcp_entity; + LOG_D(SDAP, "Updated QFI to DRB Map: QFI %u -> DRB %d \n", qfi, entity->qfi2drb_table[qfi]->rb_id); + } +} + +void nr_sdap_qfi2drb_map_del(nr_sdap_entity_t *entity, uint8_t qfi){ + entity->qfi2drb_table[qfi] = SDAP_NO_MAPPING_RULE; + LOG_D(SDAP, "Deleted QFI to DRB Map for QFI %u \n", qfi); +} + +nr_pdcp_entity_t *nr_sdap_qfi2drb_map(nr_sdap_entity_t *entity, uint8_t qfi, long upper_layer_rb_id){ + nr_pdcp_entity_t *pdcp_entity; + + pdcp_entity = entity->qfi2drb_table[qfi]; + + if(pdcp_entity){ + return pdcp_entity; + } else if(entity->default_drb) { + LOG_D(SDAP, "Mapped QFI %u to Default DRB\n", qfi); + entity->qfi2drb_map_update(entity, qfi, entity->default_drb->rb_id); + return entity->default_drb; + } else { + /* Update map for Hardcode DRB ID given from upper layer (ue/enb_tun_read_thread rb_id)*/ + entity->qfi2drb_map_update(entity, qfi, upper_layer_rb_id); + return SDAP_MAP_RULE_EMPTY; + } + + return pdcp_entity; +} + +nr_sdap_ul_hdr_t nr_sdap_construct_ctrl_pdu(uint8_t qfi){ + nr_sdap_ul_hdr_t sdap_end_marker_hdr; + sdap_end_marker_hdr.QFI = qfi; + sdap_end_marker_hdr.R = 0; + sdap_end_marker_hdr.DC = SDAP_HDR_UL_CTRL_PDU; + LOG_D(SDAP, "Constructed Control PDU with QFI:%u R:%u DC:%u \n", sdap_end_marker_hdr.QFI, + sdap_end_marker_hdr.R, + sdap_end_marker_hdr.DC); + return sdap_end_marker_hdr; +} + +nr_pdcp_entity_t *nr_sdap_map_ctrl_pdu(nr_sdap_entity_t *entity, nr_pdcp_entity_t *pdcp_entity, int map_type, uint8_t dl_qfi){ + nr_pdcp_entity_t *drb_of_endmarker = NULL; + if(map_type == SDAP_CTRL_PDU_MAP_DEF_DRB){ + drb_of_endmarker = entity->default_drb; + LOG_D(SDAP, "Mapped Control PDU to default drb\n"); + } + if(map_type == SDAP_CTRL_PDU_MAP_RULE_DRB){ + drb_of_endmarker = entity->qfi2drb_map(entity, dl_qfi, pdcp_entity->rb_id); + LOG_D(SDAP, "Mapped Control PDU according to the mapping rule, qfi %u \n", dl_qfi); + } + return drb_of_endmarker; +} + +void nr_sdap_submit_ctrl_pdu(int rnti, nr_pdcp_entity_t *sdap_ctrl_pdu_drb, nr_sdap_ul_hdr_t ctrl_pdu){ + if(sdap_ctrl_pdu_drb){ + sdap_ctrl_pdu_drb->recv_sdu(sdap_ctrl_pdu_drb, (char*)&ctrl_pdu, SDAP_HDR_LENGTH, RLC_MUI_UNDEFINED); + LOG_D(SDAP, "Sent Control PDU to PDCP Layer.\n"); + } +} + +void nr_sdap_ue_qfi2drb_config(nr_sdap_entity_t *existing_sdap_entity, + nr_pdcp_entity_t *pdcp_entity, + uint16_t rnti, + NR_QFI_t *mapped_qfi_2_add, + uint8_t mappedQFIs2AddCount, + uint8_t drb_identity) +{ + uint8_t qfi = 0; + + for(int i = 0; i < mappedQFIs2AddCount; i++){ + qfi = mapped_qfi_2_add[i]; + if(existing_sdap_entity->default_drb && existing_sdap_entity->qfi2drb_table[qfi] == SDAP_NO_MAPPING_RULE){ + nr_sdap_ul_hdr_t sdap_ctrl_pdu = existing_sdap_entity->sdap_construct_ctrl_pdu(qfi); + nr_pdcp_entity_t *sdap_ctrl_pdu_drb = existing_sdap_entity->sdap_map_ctrl_pdu(existing_sdap_entity, pdcp_entity, SDAP_CTRL_PDU_MAP_DEF_DRB, qfi); + existing_sdap_entity->sdap_submit_ctrl_pdu(rnti, sdap_ctrl_pdu_drb, sdap_ctrl_pdu); + } + if(existing_sdap_entity->qfi2drb_table[qfi]->rb_id != drb_identity && pdcp_entity->has_sdapULheader){ + nr_sdap_ul_hdr_t sdap_ctrl_pdu = existing_sdap_entity->sdap_construct_ctrl_pdu(qfi); + nr_pdcp_entity_t *sdap_ctrl_pdu_drb = existing_sdap_entity->sdap_map_ctrl_pdu(existing_sdap_entity, pdcp_entity, SDAP_CTRL_PDU_MAP_RULE_DRB, qfi); + existing_sdap_entity->sdap_submit_ctrl_pdu(rnti, sdap_ctrl_pdu_drb, sdap_ctrl_pdu); + } + } +} + +nr_sdap_entity_t *new_nr_sdap_entity(uint16_t rnti, + int pdusession_id, + boolean_t is_defaultDRB, + uint8_t drb_identity, + NR_QFI_t *mapped_qfi_2_add, + uint8_t mappedQFIs2AddCount) +{ + if(nr_sdap_get_entity(rnti, pdusession_id)) { + LOG_E(SDAP, "SDAP Entity for UE already exists.\n"); + nr_sdap_entity_t *existing_sdap_entity = nr_sdap_get_entity(rnti, pdusession_id); + nr_pdcp_entity_t *pdcp_entity = existing_sdap_entity->default_drb; + nr_sdap_ue_qfi2drb_config(existing_sdap_entity, pdcp_entity, rnti, mapped_qfi_2_add, mappedQFIs2AddCount, drb_identity); + return existing_sdap_entity; + } + + nr_sdap_entity_t *sdap_entity; + sdap_entity = calloc(1, sizeof(nr_sdap_entity_t)); + + if(sdap_entity == NULL) { + LOG_E(SDAP, "SDAP Entity creation failed, out of memory\n"); + exit(1); + } + + sdap_entity->rnti = rnti; + sdap_entity->pdusession_id = pdusession_id; + + sdap_entity->tx_entity = nr_sdap_tx_entity; + sdap_entity->rx_entity = nr_sdap_rx_entity; + + sdap_entity->sdap_construct_ctrl_pdu = nr_sdap_construct_ctrl_pdu; + sdap_entity->sdap_map_ctrl_pdu = nr_sdap_map_ctrl_pdu; + sdap_entity->sdap_submit_ctrl_pdu = nr_sdap_submit_ctrl_pdu; + + sdap_entity->qfi2drb_map_update = nr_sdap_qfi2drb_map_update; + sdap_entity->qfi2drb_map_delete = nr_sdap_qfi2drb_map_del; + sdap_entity->qfi2drb_map = nr_sdap_qfi2drb_map; + + if(is_defaultDRB) { + nr_pdcp_entity_t *pdcp_entity = nr_pdcp_find_entity_sdap(sdap_entity->rnti, drb_identity); + sdap_entity->default_drb = pdcp_entity; + LOG_I(SDAP, "Default DRB for the created SDAP entity: %d \n", sdap_entity->default_drb->rb_id); + + if(mappedQFIs2AddCount) { + for (int i = 0; i < mappedQFIs2AddCount; i++) + { + LOG_D(SDAP, "Mapped QFI to Add : %ld \n", mapped_qfi_2_add[i]); + sdap_entity->qfi2drb_map_update(sdap_entity, mapped_qfi_2_add[i], sdap_entity->default_drb->rb_id); + } + } + } + + sdap_entity->next_entity = sdap_info.sdap_entity_llist; + sdap_info.sdap_entity_llist = sdap_entity; + return sdap_entity; +} + +nr_sdap_entity_t *nr_sdap_get_entity(uint16_t rnti, int pdusession_id) { + nr_sdap_entity_t *sdap_entity; + sdap_entity = sdap_info.sdap_entity_llist; + + if(sdap_entity == NULL) + return NULL; + + while(sdap_entity->rnti != rnti && sdap_entity->next_entity != NULL) { + sdap_entity = sdap_entity->next_entity; + } + + if (sdap_entity->rnti == rnti && sdap_entity->pdusession_id == pdusession_id) + return sdap_entity; + + return NULL; +} + +void delete_nr_sdap_entity(uint16_t rnti) { + nr_sdap_entity_t *entityPtr, *entityPrev = NULL; + entityPtr = sdap_info.sdap_entity_llist; + + if(entityPtr->rnti == rnti) { + sdap_info.sdap_entity_llist = sdap_info.sdap_entity_llist->next_entity; + free(entityPtr); + } else { + while(entityPtr->rnti != rnti && entityPtr->next_entity != NULL) { + entityPrev = entityPtr; + entityPtr = entityPtr->next_entity; + } + + if(entityPtr->rnti != rnti) { + entityPrev->next_entity = entityPtr->next_entity; + free(entityPtr); + } + } +} diff --git a/openair2/SDAP/nr_sdap/nr_sdap_entity.h b/openair2/SDAP/nr_sdap/nr_sdap_entity.h new file mode 100644 index 00000000000..46c9ea37749 --- /dev/null +++ b/openair2/SDAP/nr_sdap/nr_sdap_entity.h @@ -0,0 +1,167 @@ +/* + * 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 + */ + +#ifndef _NR_SDAP_ENTITY_H_ +#define _NR_SDAP_ENTITY_H_ + +#include <stdint.h> +#include "openair2/COMMON/platform_types.h" +#include "openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h" +#include "NR_RadioBearerConfig.h" + +#define SDAP_BITMASK_DC (0x80) +#define SDAP_BITMASK_R (0x40) +#define SDAP_BITMASK_QFI (0x3F) +#define SDAP_BITMASK_RQI (0x40) +#define SDAP_HDR_UL_DATA_PDU (1) +#define SDAP_HDR_UL_CTRL_PDU (0) +#define SDAP_HDR_LENGTH (1) +#define SDAP_MAX_QFI (64) +#define SDAP_MAP_RULE_EMPTY (0) +#define AVLBL_DRB (5) +#define SDAP_NO_MAPPING_RULE (0) +#define SDAP_REFLECTIVE_MAPPING (1) +#define SDAP_RQI_HANDLING (1) +#define SDAP_CTRL_PDU_MAP_DEF_DRB (0) +#define SDAP_CTRL_PDU_MAP_RULE_DRB (1) +#define SDAP_MAX_PDU (9000) + +/* + * The values of QoS Flow ID (QFI) and Reflective QoS Indication, + * are located in the PDU Session Container, which is conveyed by + * the GTP-U Extension Header. Inside the DL PDU SESSION INFORMATION frame. + * TS 38.415 Fig. 5.5.2.1-1 + */ +typedef struct nr_sdap_dl_hdr_s { + uint8_t QFI:6; + uint8_t RQI:1; + uint8_t RDI:1; +} __attribute__((packed)) nr_sdap_dl_hdr_t; + +typedef struct nr_sdap_ul_hdr_s { + uint8_t QFI:6; + uint8_t R:1; + uint8_t DC:1; +} __attribute__((packed)) nr_sdap_ul_hdr_t; + +typedef struct nr_sdap_entity_s { + uint16_t rnti; + nr_pdcp_entity_t *default_drb; + int pdusession_id; + nr_pdcp_entity_t *qfi2drb_table[SDAP_MAX_QFI]; + + void (*qfi2drb_map_update)(struct nr_sdap_entity_s *entity, uint8_t qfi, uint8_t drb); + void (*qfi2drb_map_delete)(struct nr_sdap_entity_s *entity, uint8_t qfi); + nr_pdcp_entity_t *(*qfi2drb_map)(struct nr_sdap_entity_s *entity, uint8_t qfi, long upper_layer_rb_id); + + nr_sdap_ul_hdr_t (*sdap_construct_ctrl_pdu)(uint8_t qfi); + nr_pdcp_entity_t *(*sdap_map_ctrl_pdu)(struct nr_sdap_entity_s *entity, nr_pdcp_entity_t *pdcp_entity, int map_type, uint8_t dl_qfi); + void (*sdap_submit_ctrl_pdu)(int rnti, nr_pdcp_entity_t *sdap_ctrl_pdu_drb, nr_sdap_ul_hdr_t ctrl_pdu); + + + boolean_t (*tx_entity)(struct nr_sdap_entity_s *entity, + protocol_ctxt_t *ctxt_p, + const srb_flag_t srb_flag, + const rb_id_t rb_id, + const mui_t mui, + const confirm_t confirm, + const sdu_size_t sdu_buffer_size, + unsigned char *const sdu_buffer, + const pdcp_transmission_mode_t pt_mode, + const uint32_t *sourceL2Id, + const uint32_t *destinationL2Id); + + void (*rx_entity)(struct nr_sdap_entity_s *entity, + nr_pdcp_entity_t *pdcp_entity, + int rnti, + char *buf, + int size); + + /* List of entities */ + struct nr_sdap_entity_s *next_entity; +} nr_sdap_entity_t; + +/* QFI to DRB Mapping Related Function */ +void nr_sdap_qfi2drb_map_update(nr_sdap_entity_t *entity, uint8_t qfi, uint8_t drb); + +/* QFI to DRB Mapping Related Function */ +void nr_sdap_qfi2drb_map_del(nr_sdap_entity_t *entity, uint8_t qfi); + +/* + * TS 37.324 + * 4.4 Functions + * Mapping between a QoS flow and a DRB for both DL and UL. + * + * 5.2.1 Uplink + * If there is no stored QoS flow to DRB mapping rule for the QoS flow as specified in the subclause 5.3, map the SDAP SDU to the default DRB + * else, map the SDAP SDU to the DRB according to the stored QoS flow to DRB mapping rule. + */ +nr_pdcp_entity_t *nr_sdap_qfi2drb_map(nr_sdap_entity_t *entity, uint8_t qfi, long upper_layer_rb_id); + +/* + * TS 37.324 5.3 QoS flow to DRB Mapping + * construct an end-marker control PDU, as specified in the subclause 6.2.3, for the QoS flow; + */ +nr_sdap_ul_hdr_t nr_sdap_construct_ctrl_pdu(uint8_t qfi); + +/* + * TS 37.324 5.3 QoS flow to DRB Mapping + * map the end-marker control PDU to the + * 1.) default DRB or + * 2.) DRB according to the stored QoS flow to DRB mapping rule + */ +nr_pdcp_entity_t *nr_sdap_map_ctrl_pdu(nr_sdap_entity_t *entity, nr_pdcp_entity_t *pdcp_entity, int map_type, uint8_t dl_qfi); + +/* + * TS 37.324 5.3 QoS flow to DRB Mapping + * Submit the end-marker control PDU to the lower layer. + */ +void nr_sdap_submit_ctrl_pdu(int rnti, nr_pdcp_entity_t *sdap_ctrl_pdu_drb, nr_sdap_ul_hdr_t ctrl_pdu); + +/* + * TS 37.324 5.3 QoS flow to DRB Mapping + * 5.3.1 Configuration Procedures + */ +void nr_sdap_ue_qfi2drb_config(nr_sdap_entity_t *existing_sdap_entity, + nr_pdcp_entity_t *pdcp_entity, + uint16_t rnti, + NR_QFI_t *mapped_qfi_2_add, + uint8_t mappedQFIs2AddCount, + uint8_t drb_identity); + +/* + * TS 37.324 4.4 5.1.1 SDAP entity establishment + * Establish an SDAP entity. + */ +nr_sdap_entity_t *new_nr_sdap_entity(uint16_t rnti, + int pdusession_id, + boolean_t is_defaultDRB, + uint8_t default_DRB, + NR_QFI_t *mapped_qfi_2_add, + uint8_t mappedQFIs2AddCount); + +/* Entity Handling Related Functions */ +nr_sdap_entity_t *nr_sdap_get_entity(uint16_t rnti, int pdusession_id); + +/* Entity Handling Related Functions */ +void delete_nr_sdap_entity(uint16_t rnti); + +#endif diff --git a/openair2/SDAP/nr_sdap/nr_sdap_gnb.c b/openair2/SDAP/nr_sdap/nr_sdap_gnb.c deleted file mode 100644 index 5f0a2640e96..00000000000 --- a/openair2/SDAP/nr_sdap/nr_sdap_gnb.c +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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 - */ - -#include "nr_sdap_gnb.h" -#include <openair2/LAYER2/PDCP_v10.1.0/pdcp.h> - -boolean_t sdap_gnb_data_req(protocol_ctxt_t *ctxt_p, - const srb_flag_t srb_flag, - const rb_id_t rb_id, - const mui_t mui, - const confirm_t confirm, - const sdu_size_t sdu_buffer_size, - unsigned char *const sdu_buffer, - const pdcp_transmission_mode_t pt_mode, - const uint32_t *sourceL2Id, - const uint32_t *destinationL2Id - ) { - if(sdu_buffer == NULL) { - LOG_E(PDCP, "%s:%d:%s: SDAP Layer gNB - NULL sdu_buffer \n", __FILE__, __LINE__, __FUNCTION__); - exit(1); - } - - if(sdu_buffer_size == 0) { - LOG_E(PDCP, "%s:%d:%s: SDAP Layer gNB - NULL or 0 sdu_buffer_size \n", __FILE__, __LINE__, __FUNCTION__); - exit(1); - } - - uint8_t *sdap_buf = (uint8_t *)malloc(sdu_buffer_size+SDAP_HDR_LENGTH); - nr_sdap_dl_hdr_t sdap_hdr; - sdap_hdr.RDI = 0; // SDAP_Hardcoded - - sdap_hdr.RQI = 0; // SDAP_Hardcoded - Should get this info from DL_PDU_SESSION_INFORMATION - sdap_hdr.QFI = 1; // SDAP_Hardcoded - Should get this info from DL_PDU_SESSION_INFORMATION - memcpy(&sdap_buf[0], &sdap_hdr, 1); - memcpy(&sdap_buf[1], sdu_buffer, sdu_buffer_size); - rb_id_t sdap_drb_id = rb_id; // SDAP_Hardcoded - Should get this info from QFI to DRB mapping table - boolean_t ret = pdcp_data_req(ctxt_p, - srb_flag, - sdap_drb_id, - mui, - confirm, - sdu_buffer_size+1, - sdap_buf, - pt_mode, - sourceL2Id, - destinationL2Id); - - if(!ret) { - LOG_E(PDCP, "%s:%d:%s: SDAP Layer gNB - PDCP DL refused PDU\n", __FILE__, __LINE__, __FUNCTION__); - free(sdap_buf); - return 0; - } - - free(sdap_buf); - return 1; -} - -void sdap_gnb_ul_header_handler(char sdap_gnb_ul_hdr) { - nr_sdap_ul_hdr_t *sdap_hdr_ul = (nr_sdap_ul_hdr_t *)&sdap_gnb_ul_hdr; - - switch (sdap_hdr_ul->DC) { - case SDAP_HDR_UL_DATA_PDU: - LOG_I(PDCP, "%s:%d:%s: SDAP Layer gNB - UL Received SDAP Data PDU\n", __FILE__, __LINE__, __FUNCTION__); - break; - - case SDAP_HDR_UL_CTRL_PDU: - LOG_I(PDCP, "%s:%d:%s: SDAP Layer gNB - Received SDAP Control PDU\n", __FILE__, __LINE__, __FUNCTION__); - break; - } -} \ No newline at end of file diff --git a/openair2/SDAP/nr_sdap/nr_sdap_gnb.h b/openair2/SDAP/nr_sdap/nr_sdap_gnb.h deleted file mode 100644 index 243aa890108..00000000000 --- a/openair2/SDAP/nr_sdap/nr_sdap_gnb.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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 - */ - -#ifndef _NR_SDAP_GNB_ -#define _NR_SDAP_GNB_ - -#include "openair2/COMMON/platform_types.h" -#include "common/utils/LOG/log.h" - -#define SDAP_BITMASK_DC (0x80) -#define SDAP_BITMASK_R (0x40) -#define SDAP_BITMASK_QFI (0x3F) -#define SDAP_HDR_UL_DATA_PDU (1) -#define SDAP_HDR_UL_CTRL_PDU (0) -#define SDAP_HDR_LENGTH (1) - -typedef struct nr_sdap_dl_hdr_s { - uint8_t QFI:6; - uint8_t RQI:1; - uint8_t RDI:1; -} __attribute__((packed)) nr_sdap_dl_hdr_t; - -typedef struct nr_sdap_ul_hdr_s { - uint8_t QFI:6; - uint8_t R:1; - uint8_t DC:1; -} __attribute__((packed)) nr_sdap_ul_hdr_t; - -boolean_t sdap_gnb_data_req(protocol_ctxt_t *ctxt_p, - const srb_flag_t srb_flag, - const rb_id_t rb_id, - const mui_t mui, - const confirm_t confirm, - const sdu_size_t sdu_buffer_size, - unsigned char *const sdu_buffer, - const pdcp_transmission_mode_t pt_mode, - const uint32_t *sourceL2Id, - const uint32_t *destinationL2Id - ); - -void sdap_gnb_ul_header_handler(char sdap_gnb_ul_hdr); - -#endif \ No newline at end of file diff --git a/openair2/SDAP/nr_sdap/sdap_gNB.c b/openair2/SDAP/nr_sdap/sdap_gNB.c deleted file mode 100644 index ad84e4c273a..00000000000 --- a/openair2/SDAP/nr_sdap/sdap_gNB.c +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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 sdap_gNB.c - * \brief sdap tasks for gNB - * \author Konstantinos Alexandris <Konstantinos.Alexandris@eurecom.fr>, Cedric Roux <Cedric.Roux@eurecom.fr>, Navid Nikaein <Navid.Nikaein@eurecom.fr> - * \date 2018 - * \version 1.0 - */ -#define SDAP_GNB -#define SDAP_GNB_C - diff --git a/openair3/ocp-gtpu/gtp_itf.cpp b/openair3/ocp-gtpu/gtp_itf.cpp index cc41de3550b..3c6c63c6a42 100644 --- a/openair3/ocp-gtpu/gtp_itf.cpp +++ b/openair3/ocp-gtpu/gtp_itf.cpp @@ -17,7 +17,7 @@ extern "C" { #include <openair2/COMMON/gtpv1_u_messages_types.h> #include <openair3/ocp-gtpu/gtp_itf.h> #include <openair2/LAYER2/PDCP_v10.1.0/pdcp.h> -#include "openair2/SDAP/nr_sdap/nr_sdap_gnb.h" +#include "openair2/SDAP/nr_sdap/nr_sdap.h" //#include <openair1/PHY/phy_extern.h> #pragma pack(1) @@ -34,8 +34,35 @@ typedef struct Gtpv1uMsgHeader { teid_t teid; } __attribute__((packed)) Gtpv1uMsgHeaderT; +typedef struct Gtpv1uMsgHeaderOptFields { + uint8_t seqNum1Oct; + uint8_t seqNum2Oct; + uint8_t NPDUNum; + uint8_t NextExtHeaderType; +} __attribute__((packed)) Gtpv1uMsgHeaderOptFieldsT; + +typedef struct PDUSessionContainer { + uint8_t spare:4; + uint8_t PDU_type:4; + uint8_t QFI:6; + uint8_t RQI:1; + uint8_t PPP:1; +} __attribute__((packed)) PDUSessionContainerT; + +typedef struct Gtpv1uExtHeader { + uint8_t ExtHeaderLen; + PDUSessionContainerT pdusession_cntr; + //uint8_t NextExtHeaderType; +}__attribute__((packed)) Gtpv1uExtHeaderT; + #pragma pack() +// TS 29.281, fig 5.2.1-3 +#define PDU_SESSION_CONTAINER (0x85) +// TS 29.281, 5.2.1 +#define EXT_HDR_LNTH_OCTET_UNITS (4) +#define NO_MORE_EXT_HDRS (0) + // TS 29.060, table 7.1 defines the possible message types // here are all the possible messages (3GPP R16) #define GTP_ECHO_REQ (1) @@ -65,6 +92,7 @@ typedef struct { rnti_t rnti; ebi_t incoming_rb_id; gtpCallback callBack; + int pdusession_id; } rntiData_t; class gtpEndPoint { @@ -449,6 +477,8 @@ teid_t newGtpuCreateTunnel(instance_t instance, rnti_t rnti, int incoming_bearer inst->te2ue_mapping[incoming_teid].callBack=callBack; + inst->te2ue_mapping[incoming_teid].pdusession_id = (uint8_t)outgoing_bearer_id; + gtpv1u_bearer_t *tmp=&inst->ue2te_mapping[rnti].bearers[outgoing_bearer_id]; int addrs_length_in_bytes = remoteAddr.length / 8; @@ -574,7 +604,7 @@ int gtpv1u_create_ngu_tunnel( const instance_t instance, create_tunnel_req->pdusession_id[i], create_tunnel_req->outgoing_teid[i], create_tunnel_req->dst_addr[i], dstport, - sdap_gnb_data_req); + sdap_data_req); create_tunnel_resp->status=0; create_tunnel_resp->rnti=create_tunnel_req->rnti; create_tunnel_resp->num_tunnels=create_tunnel_req->num_tunnels; @@ -815,10 +845,28 @@ static int Gtpv1uHandleGpdu(int h, return GTPNOK; } - int offset=8; - - if( msgHdr->E || msgHdr->S ||msgHdr->PN) - offset+=8; + int offset=sizeof(Gtpv1uMsgHeaderT); + + uint8_t qfi = 0; + boolean_t rqi = FALSE; + + if( msgHdr->E || msgHdr->S || msgHdr->PN){ + Gtpv1uMsgHeaderOptFieldsT *msgHdrOpt = (Gtpv1uMsgHeaderOptFieldsT *)(msgBuf+offset); + offset+=sizeof(Gtpv1uMsgHeaderOptFieldsT); + if( msgHdr->E && msgHdrOpt->NextExtHeaderType == PDU_SESSION_CONTAINER){ + Gtpv1uExtHeaderT *msgHdrExt = (Gtpv1uExtHeaderT *)(msgBuf+offset); + offset+=msgHdrExt->ExtHeaderLen*EXT_HDR_LNTH_OCTET_UNITS; + qfi = msgHdrExt->pdusession_cntr.QFI; + rqi = msgHdrExt->pdusession_cntr.RQI; + + /* + * Check if the next extension header type of GTP extension header is set to 0 + * We can not put it in the struct Gtpv1uExtHeaderT because the length is dynamic. + */ + if(*(msgBuf+offset-1) != NO_MORE_EXT_HDRS) + LOG_W(GTPU, "Warning - Next extension header is not zero, handle it \n"); + } + } // This context is not good for gtp // frame, ... has no meaning @@ -828,6 +876,9 @@ static int Gtpv1uHandleGpdu(int h, ctxt.enb_flag = 1; ctxt.instance = inst->addr.originInstance; ctxt.rnti = tunnel->second.rnti; + ctxt.sdap.qfi = qfi; + ctxt.sdap.rqi = rqi; + ctxt.sdap.pdusession_id = tunnel->second.pdusession_id; ctxt.frame = 0; ctxt.subframe = 0; ctxt.eNB_index = 0; -- GitLab