GTPU: add an API to better deal with extension headers
And adapt existing code to use it. Only for transmission for the moment.
This commit is contained in:
committed by
Robert Schmidt
parent
33cbbafddb
commit
c35f07b0b9
+2
-1
@@ -1395,9 +1395,10 @@ include_directories("${OPENAIR1_DIR}/SCHED_NR_UE")
|
||||
add_library (GTPV1U
|
||||
${RRC_DIR}/rrc_eNB_GTPV1U.c
|
||||
${OPENAIR3_DIR}/ocp-gtpu/gtp_itf.cpp
|
||||
${OPENAIR3_DIR}/ocp-gtpu/gtpu_extensions.c
|
||||
)
|
||||
target_link_libraries(GTPV1U PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs)
|
||||
target_link_libraries(GTPV1U PRIVATE SIMU)
|
||||
target_link_libraries(GTPV1U PRIVATE SIMU ds)
|
||||
include_directories(${OPENAIR3_DIR}/ocp-gtp)
|
||||
|
||||
set (MME_APP_SRC
|
||||
|
||||
@@ -21,6 +21,7 @@ extern "C" {
|
||||
#include <openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h>
|
||||
#include <openair2/LAYER2/nr_rlc/nr_rlc_oai_api.h>
|
||||
#include "openair2/SDAP/nr_sdap/nr_sdap.h"
|
||||
#include "openair3/ocp-gtpu/gtpu_extensions.h"
|
||||
#include "sim.h"
|
||||
|
||||
#pragma pack(1)
|
||||
@@ -230,11 +231,9 @@ static int gtpv1uCreateAndSendMsg(gtpv1u_bearer_t *bearer,
|
||||
int msgLen,
|
||||
bool seqNumFlag,
|
||||
bool npduNumFlag,
|
||||
int extHdrType,
|
||||
uint8_t *extensionHeader_buffer,
|
||||
uint8_t extensionHeader_length)
|
||||
gtpu_extension_header_t *extensions,
|
||||
int extensions_count)
|
||||
{
|
||||
|
||||
DevAssert(msgLen + HDR_MAX < 65536); // maximum size of UDP packet
|
||||
uint8_t buffer[msgLen + HDR_MAX];
|
||||
uint8_t *curPtr = buffer;
|
||||
@@ -242,7 +241,7 @@ static int gtpv1uCreateAndSendMsg(gtpv1u_bearer_t *bearer,
|
||||
// N should be 0 for us (it was used only in 2G and 3G)
|
||||
msgHdr->PN = npduNumFlag;
|
||||
msgHdr->S = seqNumFlag;
|
||||
msgHdr->E = extHdrType != NO_MORE_EXT_HDRS;
|
||||
msgHdr->E = extensions_count != 0;
|
||||
msgHdr->spare = 0;
|
||||
// PT=0 is for GTP' TS 32.295 (charging)
|
||||
msgHdr->PT = 1;
|
||||
@@ -252,32 +251,32 @@ static int gtpv1uCreateAndSendMsg(gtpv1u_bearer_t *bearer,
|
||||
|
||||
curPtr += sizeof(Gtpv1uMsgHeaderT);
|
||||
|
||||
if (seqNumFlag || (extHdrType != NO_MORE_EXT_HDRS) || npduNumFlag) {
|
||||
if (msgHdr->PN || msgHdr->S || msgHdr->E) {
|
||||
*(uint16_t *)curPtr = seqNumFlag ? bearer->seqNum : 0x0000;
|
||||
curPtr += sizeof(uint16_t);
|
||||
*(uint8_t *)curPtr = npduNumFlag ? bearer->npduNum : 0x00;
|
||||
curPtr++;
|
||||
*(uint8_t *)curPtr = extHdrType;
|
||||
*curPtr = extensions_count ? serialize_gtpu_extension_type(extensions[0].type) : 0;
|
||||
curPtr++;
|
||||
}
|
||||
|
||||
// Bug: if there is more than one extension, infinite loop on extensionHeader_buffer
|
||||
while (extHdrType != NO_MORE_EXT_HDRS) {
|
||||
if (extensionHeader_length > 0) {
|
||||
memcpy(curPtr, extensionHeader_buffer, extensionHeader_length);
|
||||
curPtr += extensionHeader_length;
|
||||
LOG_D(GTPU,
|
||||
"Extension Header for DDD added. The length is: %d, extension header type is: %x \n",
|
||||
extensionHeader_length,
|
||||
*((uint8_t *)(buffer + 11)));
|
||||
extHdrType = extensionHeader_buffer[extensionHeader_length - 1];
|
||||
LOG_D(GTPU, "Next extension header type is: %x \n", *((uint8_t *)(buffer + 11)));
|
||||
} else {
|
||||
LOG_W(GTPU, "Extension header type not supported, returning... \n");
|
||||
for (int i = 0; i < extensions_count; i++) {
|
||||
int available_size = sizeof(buffer) - (curPtr - buffer);
|
||||
gtpu_extension_header_type_t next = i == extensions_count - 1 ? GTPU_EXT_NONE : extensions[i + 1].type;
|
||||
int len = serialize_extension(&extensions[i], next, curPtr, available_size);
|
||||
if (len == -1) {
|
||||
LOG_E(GTPU, "GTP extension serialization: buffer too small\n");
|
||||
return GTPNOK;
|
||||
}
|
||||
curPtr += len;
|
||||
}
|
||||
|
||||
if (Msg != NULL) {
|
||||
int available_size = sizeof(buffer) - (curPtr - buffer);
|
||||
if (msgLen > available_size) {
|
||||
LOG_E(GTPU, "GTP message creation: buffer too small\n");
|
||||
return GTPNOK;
|
||||
}
|
||||
memcpy(curPtr, Msg, msgLen);
|
||||
curPtr += msgLen;
|
||||
}
|
||||
@@ -347,18 +346,22 @@ void gtpv1uSendDirect(instance_t instance,
|
||||
gtpv1u_bearer_t bearer = ptr2->second;
|
||||
pthread_mutex_unlock(&globGtp.gtp_lock);
|
||||
|
||||
Gtpv1uExtHeaderT *opt_ext = NULL;
|
||||
Gtpv1uExtHeaderT ext = {0};
|
||||
int extension_count = 0;
|
||||
gtpu_extension_header_t ext = {};
|
||||
if (bearer.outgoing_qfi != -1) {
|
||||
opt_ext = &ext;
|
||||
ext.ExtHeaderLen = 1; // in quad bytes EXT_HDR_LNTH_OCTET_UNITS
|
||||
ext.pdusession_cntr.spare = 0;
|
||||
ext.pdusession_cntr.PDU_type = UL_PDU_SESSION_INFORMATION;
|
||||
ext.pdusession_cntr.QFI = bearer.outgoing_qfi;
|
||||
ext.pdusession_cntr.Reflective_QoS_activation = false;
|
||||
ext.pdusession_cntr.Paging_Policy_Indicator = false;
|
||||
ext.NextExtHeaderType = NO_MORE_EXT_HDRS;
|
||||
|
||||
ext = {
|
||||
.type = GTPU_EXT_UL_PDU_SESSION_INFORMATION,
|
||||
.ul_pdu_session_information = {
|
||||
.qmp = false,
|
||||
.dl_delay_ind = false,
|
||||
.ul_delay_ind = false,
|
||||
.snp = false,
|
||||
.n3n9_delay_ind = false,
|
||||
.new_ie_flag = false,
|
||||
.qfi = bearer.outgoing_qfi
|
||||
}
|
||||
};
|
||||
extension_count++;
|
||||
}
|
||||
|
||||
DevAssert(compatInst(instance) == bearer.sock_fd);
|
||||
@@ -368,9 +371,30 @@ void gtpv1uSendDirect(instance_t instance,
|
||||
len,
|
||||
seqNumFlag,
|
||||
npduNumFlag,
|
||||
opt_ext ? PDU_SESSION_CONTAINER : NO_MORE_EXT_HDRS,
|
||||
(uint8_t *)opt_ext,
|
||||
sizeof(*opt_ext));
|
||||
&ext,
|
||||
extension_count);
|
||||
}
|
||||
|
||||
static void fillDlDeliveryStatusReport(gtpu_extension_header_t *ext, uint32_t RLC_buffer_availability, uint32_t NR_PDCP_PDU_SN)
|
||||
{
|
||||
*ext = {
|
||||
.type = GTPU_EXT_DL_DATA_DELIVERY_STATUS,
|
||||
.dl_data_delivery_status = {
|
||||
/* previous version of the code was sending highest_transmitted_nr_pdcp_sn if
|
||||
* it is != 0, let's do the same for the moment */
|
||||
.highest_transmitted_nr_pdcp_sn_ind = NR_PDCP_PDU_SN != 0,
|
||||
.highest_delivered_nr_pdcp_sn_ind = false,
|
||||
.final_frame_ind = false,
|
||||
.lost_packet_report = false,
|
||||
.delivered_nr_pdcp_sn_range_ind = false,
|
||||
.data_rate_ind = false,
|
||||
.retransmitted_nr_pdcp_sn_ind = false,
|
||||
.delivered_retransmitted_nr_pdcp_ind = false,
|
||||
.cause_report = false,
|
||||
.desired_buffer_size = RLC_buffer_availability,
|
||||
.highest_transmitted_nr_pdcp_sn = NR_PDCP_PDU_SN
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static void gtpv1uEndTunnel(instance_t instance, gtpv1u_enb_end_marker_req_t *req)
|
||||
@@ -950,7 +974,6 @@ static int Gtpv1uHandleEchoReq(int h, uint8_t *msgBuf, uint32_t msgBufLen, const
|
||||
sizeof recovery,
|
||||
true,
|
||||
false,
|
||||
NO_MORE_EXT_HDRS,
|
||||
NULL,
|
||||
0);
|
||||
}
|
||||
@@ -1168,37 +1191,8 @@ static int Gtpv1uHandleGpdu(int h, uint8_t *msgBuf, uint32_t msgBufLen, const st
|
||||
LOG_D(GTPU, "Create and send DL DATA Delivery status for the previously received PDU, NR_PDCP_PDU_SN: %u \n", NR_PDCP_PDU_SN);
|
||||
int rlc_tx_buffer_space = nr_rlc_get_available_tx_space(ctxt.rntiMaybeUEid, rb_id + 3);
|
||||
LOG_D(GTPU, "Available buffer size in RLC for Tx: %d \n", rlc_tx_buffer_space);
|
||||
/*Total size of DDD_status PDU = 1 octet to report extension header length
|
||||
* size of mandatory part + 3 octets for highest transmitted/delivered PDCP SN
|
||||
* 1 octet for padding + 1 octet for next extension header type,
|
||||
* according to TS 38.425: Fig. 5.5.2.2-1 and section 5.5.3.24*/
|
||||
extensionHeader_t *extensionHeader;
|
||||
extensionHeader = (extensionHeader_t *)calloc(1, sizeof(extensionHeader_t));
|
||||
extensionHeader->buffer[0] = (1 + sizeof(DlDataDeliveryStatus_flagsT) + 3 + 1 + 1) / 4;
|
||||
DlDataDeliveryStatus_flagsT DlDataDeliveryStatus;
|
||||
DlDataDeliveryStatus.deliveredPdcpSn = 0;
|
||||
DlDataDeliveryStatus.transmittedPdcpSn = 1;
|
||||
DlDataDeliveryStatus.pduType = 1;
|
||||
DlDataDeliveryStatus.drbBufferSize =
|
||||
htonl(rlc_tx_buffer_space); // htonl(10000000); //hardcoded for now but normally we should extract it from RLC
|
||||
memcpy(extensionHeader->buffer + 1, &DlDataDeliveryStatus, sizeof(DlDataDeliveryStatus_flagsT));
|
||||
uint8_t offset = sizeof(DlDataDeliveryStatus_flagsT) + 1;
|
||||
|
||||
extensionHeader->buffer[offset] = (NR_PDCP_PDU_SN >> 16) & 0xff;
|
||||
extensionHeader->buffer[offset + 1] = (NR_PDCP_PDU_SN >> 8) & 0xff;
|
||||
extensionHeader->buffer[offset + 2] = NR_PDCP_PDU_SN & 0xff;
|
||||
LOG_D(GTPU,
|
||||
"Octets reporting NR_PDCP_PDU_SN, extensionHeader-> %u:%u:%u \n",
|
||||
extensionHeader->buffer[offset],
|
||||
extensionHeader->buffer[offset + 1],
|
||||
extensionHeader->buffer[offset + 2]);
|
||||
extensionHeader->buffer[offset + 3] = 0x00; // Padding octet
|
||||
extensionHeader->buffer[offset + 4] = 0x00; // No more extension headers
|
||||
/*Total size of DDD_status PDU = size of mandatory part +
|
||||
* 3 octets for highest transmitted/delivered PDCP SN +
|
||||
* 1 octet for padding + 1 octet for next extension header type,
|
||||
* according to TS 38.425: Fig. 5.5.2.2-1 and section 5.5.3.24*/
|
||||
extensionHeader->length = 1 + sizeof(DlDataDeliveryStatus_flagsT) + 3 + 1 + 1;
|
||||
gtpu_extension_header_t ext;
|
||||
fillDlDeliveryStatusReport(&ext, rlc_tx_buffer_space, NR_PDCP_PDU_SN);
|
||||
uint32_t teid = globGtp.te2ue_mapping[ntohl(msgHdr->teid)].outgoing_teid;
|
||||
gtpv1u_bearer_t bearer = create_bearer(h, addr, teid, 0);
|
||||
gtpv1uCreateAndSendMsg(&bearer,
|
||||
@@ -1207,9 +1201,8 @@ static int Gtpv1uHandleGpdu(int h, uint8_t *msgBuf, uint32_t msgBufLen, const st
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
NR_RAN_CONTAINER,
|
||||
extensionHeader->buffer,
|
||||
extensionHeader->length);
|
||||
&ext,
|
||||
1);
|
||||
}
|
||||
|
||||
LOG_D(GTPU, "[%d] Received a %d bytes packet for: teid:%x\n", h, msgBufLen - offset, ntohl(msgHdr->teid));
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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 "gtpu_extensions.h"
|
||||
#include "common/utils/assertions.h"
|
||||
#include "common/utils/ds/byte_array_producer.h"
|
||||
#include "LOG/log.h"
|
||||
|
||||
/* 29.281 Figure 5.2.1-3 */
|
||||
#define NR_RAN_CONTAINER 0x84
|
||||
#define PDU_SESSION_CONTAINER 0x85
|
||||
|
||||
/* from an extension type, returns its "extension header type"
|
||||
* as defined in 29.281 Figure 5.2.1-3
|
||||
*/
|
||||
int serialize_gtpu_extension_type(gtpu_extension_header_type_t type)
|
||||
{
|
||||
switch (type) {
|
||||
case GTPU_EXT_NONE:
|
||||
/* 0, no more extension */
|
||||
return 0;
|
||||
case GTPU_EXT_UL_PDU_SESSION_INFORMATION:
|
||||
return PDU_SESSION_CONTAINER;
|
||||
case GTPU_EXT_DL_DATA_DELIVERY_STATUS:
|
||||
return NR_RAN_CONTAINER;
|
||||
default:
|
||||
AssertFatal(0, "unknown GTPU extension type %d\n", type);
|
||||
}
|
||||
}
|
||||
|
||||
/* returns 0 on error, 1 on success */
|
||||
static int serialize_ul_pdu_session_information(byte_array_producer_t *b, ul_pdu_session_information_t *ext)
|
||||
{
|
||||
/* see 38.415 5.5.2.2 */
|
||||
AssertFatal(!ext->qmp && !ext->dl_delay_ind && !ext->ul_delay_ind
|
||||
&& !ext->snp && !ext->n3n9_delay_ind && !ext->new_ie_flag,
|
||||
"todo\n");
|
||||
|
||||
uint8_t b1 = (1 << 4) | (ext->qmp << 3) | (ext->dl_delay_ind << 2) | (ext->ul_delay_ind << 1) | ext->snp;
|
||||
uint8_t b2 = (ext->n3n9_delay_ind << 7) | (ext->new_ie_flag << 6) | ext->qfi;
|
||||
return byte_array_producer_put_byte(b, b1) && byte_array_producer_put_byte(b, b2);
|
||||
}
|
||||
|
||||
/* returns 0 on error, 1 on success */
|
||||
static int serialize_dl_data_delivery_status(byte_array_producer_t *b, dl_data_delivery_status_t *ext)
|
||||
{
|
||||
/* see 38.425 5.5.2.2 */
|
||||
AssertFatal(!ext->highest_delivered_nr_pdcp_sn_ind && !ext->final_frame_ind
|
||||
&& !ext->lost_packet_report && !ext->delivered_nr_pdcp_sn_range_ind
|
||||
&& !ext->data_rate_ind && !ext->retransmitted_nr_pdcp_sn_ind
|
||||
&& !ext->delivered_retransmitted_nr_pdcp_ind && !ext->cause_report,
|
||||
"todo\n");
|
||||
|
||||
uint8_t b1 = (1 << 4)
|
||||
| (ext->highest_transmitted_nr_pdcp_sn_ind << 3)
|
||||
| (ext->highest_delivered_nr_pdcp_sn_ind << 2)
|
||||
| (ext->final_frame_ind << 1)
|
||||
| ext->lost_packet_report;
|
||||
uint8_t b2 = (ext->delivered_nr_pdcp_sn_range_ind << 4)
|
||||
| (ext->data_rate_ind << 3)
|
||||
| (ext->retransmitted_nr_pdcp_sn_ind << 2)
|
||||
| (ext->delivered_retransmitted_nr_pdcp_ind << 1)
|
||||
| ext->cause_report;
|
||||
if (!byte_array_producer_put_byte(b, b1) || !byte_array_producer_put_byte(b, b2))
|
||||
return 0;
|
||||
|
||||
if (!byte_array_producer_put_u32_be(b, ext->desired_buffer_size))
|
||||
return 0;
|
||||
|
||||
if (ext->highest_transmitted_nr_pdcp_sn_ind)
|
||||
if (!byte_array_producer_put_u24_be(b, ext->highest_transmitted_nr_pdcp_sn))
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* returns -1 on error, number of serialized bytes on success */
|
||||
int serialize_extension(gtpu_extension_header_t *ext, gtpu_extension_header_type_t next, uint8_t *out_buf, int out_len)
|
||||
{
|
||||
byte_array_producer_t b;
|
||||
|
||||
b = byte_array_producer_from_buffer(out_buf, out_len);
|
||||
/* length - will be set later */
|
||||
if (!byte_array_producer_put_byte(&b, 0))
|
||||
goto error;
|
||||
|
||||
switch (ext->type) {
|
||||
case GTPU_EXT_UL_PDU_SESSION_INFORMATION:
|
||||
if (!serialize_ul_pdu_session_information(&b, &ext->ul_pdu_session_information))
|
||||
goto error;
|
||||
break;
|
||||
case GTPU_EXT_DL_DATA_DELIVERY_STATUS:
|
||||
if (!serialize_dl_data_delivery_status(&b, &ext->dl_data_delivery_status))
|
||||
goto error;
|
||||
break;
|
||||
default:
|
||||
LOG_E(GTPU, "unknown extension type %d\n", ext->type);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* padding */
|
||||
while ((b.pos & 3) != 3)
|
||||
if (!byte_array_producer_put_byte(&b, 0))
|
||||
goto error;
|
||||
|
||||
/* next */
|
||||
if (!byte_array_producer_put_byte(&b, serialize_gtpu_extension_type(next)))
|
||||
goto error;
|
||||
|
||||
/* length is now know */
|
||||
DevAssert(b.pos / 4 <= 255);
|
||||
out_buf[0] = b.pos / 4;
|
||||
|
||||
return b.pos;
|
||||
|
||||
error:
|
||||
LOG_E(GTPU, "error serializing extension\n");
|
||||
return -1;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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 GTPU_EXTENSIONS_H
|
||||
#define GTPU_EXTENSIONS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef enum {
|
||||
GTPU_EXT_NONE,
|
||||
/* 38.415 */
|
||||
GTPU_EXT_UL_PDU_SESSION_INFORMATION,
|
||||
/* 38.425 */
|
||||
GTPU_EXT_DL_DATA_DELIVERY_STATUS,
|
||||
} gtpu_extension_header_type_t;
|
||||
|
||||
/* 38.415 */
|
||||
typedef struct {
|
||||
/* not all fields are present, to be refined if needed */
|
||||
bool qmp;
|
||||
bool dl_delay_ind;
|
||||
bool ul_delay_ind;
|
||||
bool snp;
|
||||
bool n3n9_delay_ind;
|
||||
bool new_ie_flag;
|
||||
int qfi;
|
||||
} ul_pdu_session_information_t;
|
||||
|
||||
/* 38.425 */
|
||||
typedef struct {
|
||||
/* not all fields are present, to be refined if needed */
|
||||
bool highest_transmitted_nr_pdcp_sn_ind;
|
||||
bool highest_delivered_nr_pdcp_sn_ind;
|
||||
bool final_frame_ind;
|
||||
bool lost_packet_report;
|
||||
bool delivered_nr_pdcp_sn_range_ind;
|
||||
bool data_rate_ind;
|
||||
bool retransmitted_nr_pdcp_sn_ind;
|
||||
bool delivered_retransmitted_nr_pdcp_ind;
|
||||
bool cause_report;
|
||||
uint32_t desired_buffer_size;
|
||||
uint32_t highest_transmitted_nr_pdcp_sn;
|
||||
} dl_data_delivery_status_t;
|
||||
|
||||
typedef struct {
|
||||
gtpu_extension_header_type_t type;
|
||||
union {
|
||||
ul_pdu_session_information_t ul_pdu_session_information;
|
||||
dl_data_delivery_status_t dl_data_delivery_status;
|
||||
};
|
||||
} gtpu_extension_header_t;
|
||||
|
||||
int serialize_gtpu_extension_type(gtpu_extension_header_type_t type);
|
||||
int serialize_extension(gtpu_extension_header_t *ext, gtpu_extension_header_type_t next, uint8_t *out_buf, int out_len);
|
||||
|
||||
#endif /* GTPU_EXTENSIONS_H */
|
||||
Reference in New Issue
Block a user