neels has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmo-upf/+/27222 )
Change subject: libosmo-pfcp: implement/generate TLV and IE value coding ......................................................................
libosmo-pfcp: implement/generate TLV and IE value coding
Related: SYS#5599 Change-Id: I3069045b2d42dac88d955c636230adc64a7a4aa7 --- M include/osmocom/pfcp/Makefile.am A include/osmocom/pfcp/pfcp_ies_custom.h M src/libosmo-pfcp/Makefile.am A src/libosmo-pfcp/gen__pfcp_ies_auto.c A src/libosmo-pfcp/pfcp_ies_custom.c 5 files changed, 1,360 insertions(+), 0 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmo-upf refs/changes/22/27222/1
diff --git a/include/osmocom/pfcp/Makefile.am b/include/osmocom/pfcp/Makefile.am index ff7df5e..5e551d4 100644 --- a/include/osmocom/pfcp/Makefile.am +++ b/include/osmocom/pfcp/Makefile.am @@ -1,6 +1,22 @@ pfcp_HEADERS = \ + pfcp_ies_custom.h \ pfcp_proto.h \ pfcp_strs.h \ $(NULL)
pfcpdir = $(includedir)/osmocom/pfcp + +BUILT_SOURCES = \ + pfcp_ies_auto.h \ + $(NULL) + +CLEANFILES = \ + pfcp_ies_auto.h \ + $(NULL) + +pfcp_ies_auto.h: $(top_srcdir)/src/libosmo-pfcp/gen__pfcp_ies_auto.c \ + $(top_srcdir)/src/libosmo-tlv/tlv_gen.c \ + $(top_srcdir)/include/osmocom/tlv/tlv_gen.h + $(MAKE) -C $(top_builddir)/src/libosmo-tlv + $(MAKE) -C $(top_builddir)/src/libosmo-pfcp gen__pfcp_ies_auto + $(top_builddir)/src/libosmo-pfcp/gen__pfcp_ies_auto h > $(builddir)/pfcp_ies_auto.h diff --git a/include/osmocom/pfcp/pfcp_ies_custom.h b/include/osmocom/pfcp/pfcp_ies_custom.h new file mode 100644 index 0000000..940afbe --- /dev/null +++ b/include/osmocom/pfcp/pfcp_ies_custom.h @@ -0,0 +1,135 @@ +/* Definitions for decoded PFCP IEs, to be used by the auto-generated pfcp_ies_auto.c. */ +#pragma once + +#include <osmocom/core/socket.h> + +#include <osmocom/pfcp/pfcp_proto.h> + +/* Common pattern used in various PFCP IEs. */ +struct osmo_pfcp_ip_addrs { + bool v4_present; + struct osmo_sockaddr v4; + bool v6_present; + struct osmo_sockaddr v6; +}; + +int osmo_pfcp_ip_addrs_set(struct osmo_pfcp_ip_addrs *dst, const struct osmo_sockaddr *addr); + +/* 3GPP TS 29.244 8.2.38, IETF RFC 1035 3.1 */ +struct osmo_pfcp_ie_node_id { + enum osmo_pfcp_node_id_type type; + union { + struct osmo_sockaddr ip; + /* Fully qualified domain name in "dot" notation ("host.example.com") */ + char fqdn[254]; + }; +}; + +/* 3GPP TS 29.244 8.2.25 + * Usage: + * struct osmo_pfcp_ie_up_function_features x; + * osmo_pfcp_bits_set(x.bits, OSMO_PFCP_UP_FEAT_BUNDL, true); + * if (osmo_pfcp_bits_get(x.bits, OSMO_PFCP_UP_FEAT_BUNDL)) + * foo(); + * printf("%s\n", osmo_pfcp_bits_to_str_c(x.bits, osmo_pfcp_up_feature_strs)); + */ +struct osmo_pfcp_ie_up_function_features { + uint8_t bits[6]; +}; + +/* 3GPP TS 29.244 8.2.58 + * struct osmo_pfcp_ie_cp_function_features x; + * osmo_pfcp_bits_set(x.bits, OSMO_PFCP_CP_FEAT_BUNDL, true); + * if (osmo_pfcp_bits_get(x.bits, OSMO_PFCP_CP_FEAT_BUNDL)) + * foo(); + * printf("%s\n", osmo_pfcp_bits_to_str_c(x.bits, osmo_pfcp_cp_feature_strs)); + */ +struct osmo_pfcp_ie_cp_function_features { + uint8_t bits[3]; +}; + +/* 3GPP TS 29.244 8.2.37 */ +struct osmo_pfcp_ie_f_seid { + uint64_t seid; + struct osmo_pfcp_ip_addrs ip_addr; +}; + +void osmo_pfcp_ie_f_seid_set(struct osmo_pfcp_ie_f_seid *f_seid, uint64_t seid, + const struct osmo_sockaddr *remote_addr); + +/* 3GPP TS 29.244 8.3.2 */ +struct osmo_pfcp_ie_f_teid { + bool choose_flag; + union { + struct { + uint32_t teid; + struct osmo_pfcp_ip_addrs ip_addr; + } fixed; + struct { + bool ipv4_addr; + bool ipv6_addr; + bool choose_id_present; + uint8_t choose_id; + } choose; + }; +}; + +/* 3GPP TS 29.244 8.2.62 */ +struct osmo_pfcp_ie_ue_ip_address { + bool chv6; + bool chv4; + bool ip_is_destination; + struct osmo_pfcp_ip_addrs ip_addr; + bool ipv6_prefix_delegation_bits_present; + uint8_t ipv6_prefix_delegation_bits; + bool ipv6_prefix_length_present; + uint8_t ipv6_prefix_length; +}; + +/* 3GPP TS 29.244 8.2.26. + * Usage: + * struct osmo_pfcp_ie_apply_action x; + * osmo_pfcp_bits_set(x.bits, OSMO_PFCP_APPLY_ACTION_FORW, true); + * if (osmo_pfcp_bits_get(x.bits, OSMO_PFCP_APPLY_ACTION_FORW)) + * foo(); + * printf("%s\n", osmo_pfcp_bits_to_str_c(x.bits, osmo_pfcp_apply_action_strs)); + */ +struct osmo_pfcp_ie_apply_action { + uint8_t bits[2]; +}; + +struct osmo_pfcp_ie_network_inst { + /* A domain name may have up to 253 characters; plus nul. */ + char str[253+1]; +}; + +struct osmo_pfcp_ie_activate_predefined_rules { + char str[256]; +}; + +/* 3GPP TS 29.244 8.2.56 */ +struct osmo_pfcp_ie_outer_header_creation { + /* desc_bits Usage: + * osmo_pfcp_bits_set(x.desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV4, true); + * if (osmo_pfcp_bits_get(x.desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV4)) + * foo(); + * printf("%s\n", osmo_pfcp_bits_to_str_c(x.desc_bits, osmo_pfcp_outer_header_creation_strs)); + */ + uint8_t desc_bits[2]; + bool teid_present; + uint32_t teid; + struct osmo_pfcp_ip_addrs ip_addr; + bool port_number_present; + uint16_t port_number; + bool c_tag_present; + uint32_t c_tag; + bool s_tag_present; + uint32_t s_tag; +}; + +/* 3GPP TS 29.244 8.2.64. */ +struct osmo_pfcp_ie_outer_header_removal { + enum osmo_pfcp_outer_header_removal_desc desc; + bool gtp_u_extension_header_del_present; + uint8_t gtp_u_extension_header_del_bits[1]; +}; diff --git a/src/libosmo-pfcp/Makefile.am b/src/libosmo-pfcp/Makefile.am index f2dacfe..16ed680 100644 --- a/src/libosmo-pfcp/Makefile.am +++ b/src/libosmo-pfcp/Makefile.am @@ -24,5 +24,37 @@ $(NULL)
libosmo_pfcp_a_SOURCES = \ + pfcp_ies_custom.c \ pfcp_strs.c \ + \ + pfcp_ies_auto.c \ + $(NULL) + +BUILT_SOURCES = \ + pfcp_ies_auto.c \ + $(NULL) + +CLEANFILES = \ + pfcp_ies_auto.c \ + $(NULL) + +pfcp_ies_auto.c: $(srcdir)/gen__pfcp_ies_auto.c \ + $(top_srcdir)/src/libosmo-tlv/tlv_gen.c \ + $(top_srcdir)/include/osmocom/tlv/tlv_gen.h + $(MAKE) -C $(top_builddir)/src/libosmo-tlv + $(MAKE) gen__pfcp_ies_auto + $(builddir)/gen__pfcp_ies_auto c > $(builddir)/pfcp_ies_auto.c + +noinst_PROGRAMS = \ + gen__pfcp_ies_auto \ + $(NULL) + +gen__pfcp_ies_auto_SOURCES = \ + gen__pfcp_ies_auto.c \ + $(NULL) + +gen__pfcp_ies_auto_LDADD = \ + $(top_builddir)/src/libosmo-tlv/libosmo-tlv.a \ + $(LIBOSMOCORE_LIBS) \ + $(COVERAGE_LDFLAGS) \ $(NULL) diff --git a/src/libosmo-pfcp/gen__pfcp_ies_auto.c b/src/libosmo-pfcp/gen__pfcp_ies_auto.c new file mode 100644 index 0000000..c90cbdd --- /dev/null +++ b/src/libosmo-pfcp/gen__pfcp_ies_auto.c @@ -0,0 +1,448 @@ +/* Tool to generate C source code of structs and IE arrays for de- and encoding PFCP messages. */ +/* + * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH info@sysmocom.de + * All Rights Reserved. + * + * Author: Neels Janosch Hofmeyr nhofmeyr@sysmocom.de + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include <stdbool.h> +#include <stdio.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/talloc.h> +#include <osmocom/tlv/tlv_gen.h> + +/* o_ means optional */ + +#define O(IE_INST) { .optional = true, .ie = &IE_INST } +#define M(IE_INST) { .ie = &(IE_INST) } +#define MULTI(MAX, IE_INST) { .multi = MAX, .ie = &(IE_INST) } + +static const struct osmo_tlv_gen_ie recovery_time_stamp = { + "recovery_time_stamp", + "uint32_t", + .dec_enc = "32be", + .spec_ref = "7.4.2", +}; + +static const struct osmo_tlv_gen_ie node_id = { + "node_id", + .spec_ref = "8.2.38", +}; + +static const struct osmo_tlv_gen_ie up_function_features = { + "up_function_features", + .spec_ref = "8.2.25", +}; + +static const struct osmo_tlv_gen_ie cp_function_features = { + "cp_function_features", + .spec_ref = "8.2.58", +}; + +static const struct osmo_tlv_gen_ie cause = { + "cause", + "enum osmo_pfcp_cause", + .spec_ref = "8.2.1", +}; + +static const struct osmo_tlv_gen_ie offending_ie = { + "offending_ie", + .decoded_type = "enum osmo_pfcp_iei", + .spec_ref = "8.2.22", +}; + +static const struct osmo_tlv_gen_ie cp_f_seid = { + "cp_f_seid", + .tag_name = "f_seid", + .spec_ref = "8.2.37", +}; + +static const struct osmo_tlv_gen_ie up_f_seid = { + "up_f_seid", + .tag_name = "f_seid", + .spec_ref = "8.2.37", +}; + +static const struct osmo_tlv_gen_ie pdr_id = { + "pdr_id", + .decoded_type = "uint16_t", + .dec_enc = "16be", + .spec_ref = "8.2.36", +}; + +static const struct osmo_tlv_gen_ie precedence = { + "precedence", + .decoded_type = "uint32_t", + .dec_enc = "32be", + .spec_ref = "8.2.11", +}; + +static const struct osmo_tlv_gen_ie source_iface = { + "source_iface", + .decoded_type = "enum osmo_pfcp_source_iface", + .spec_ref = "8.2.2", +}; + +static const struct osmo_tlv_gen_ie source_iface_type = { + "source_iface_type", + .decoded_type = "enum osmo_pfcp_3gpp_iface_type", + .tag_name = "3gpp_iface_type", + .spec_ref = "8.2.118", +}; + +static const struct osmo_tlv_gen_ie local_f_teid = { + "local_f_teid", + .tag_name = "f_teid", + .spec_ref = "8.2.3", +}; + +static const struct osmo_tlv_gen_ie ue_ip_address = { + "ue_ip_address", + .spec_ref = "8.2.62", +}; + +static const struct osmo_tlv_gen_ie traffic_endpoint_id = { + "traffic_endpoint_id", + .decoded_type = "uint8_t", + .dec_enc = "8", + .spec_ref = "8.2.92", +}; + +static const struct osmo_tlv_gen_ie_o ies_in_pdi[] = { + M(source_iface), + O(local_f_teid), + O(ue_ip_address), + O(traffic_endpoint_id), + O(source_iface_type), + {0} +}; + +static const struct osmo_tlv_gen_ie pdi = { + "pdi", + .nested_ies = ies_in_pdi, + .spec_ref = "7.5.2.2-2", +}; + +static const struct osmo_tlv_gen_ie outer_header_removal = { + "outer_header_removal", + .spec_ref = "8.2.64", +}; + +static const struct osmo_tlv_gen_ie far_id = { + "far_id", + .decoded_type = "uint32_t", + .dec_enc = "32be", + .spec_ref = "8.2.74", +}; + +static const struct osmo_tlv_gen_ie activate_predefined_rules = { + "activate_predefined_rules", + .spec_ref = "8.2.72", +}; + +static const struct osmo_tlv_gen_ie_o ies_in_create_pdr[] = { + M(pdr_id), + M(precedence), + M(pdi), + O(outer_header_removal), + O(far_id), + O(activate_predefined_rules), + {0} +}; + +static const struct osmo_tlv_gen_ie create_pdr = { + "create_pdr", + .nested_ies = ies_in_create_pdr, + .spec_ref = "7.5.2.2", +}; + +static const struct osmo_tlv_gen_ie_o ies_in_created_pdr[] = { + M(pdr_id), + O(local_f_teid), + {0} +}; + +static const struct osmo_tlv_gen_ie created_pdr = { + "created_pdr", + .nested_ies = ies_in_created_pdr, + .spec_ref = "7.5.3.2", +}; + +static const struct osmo_tlv_gen_ie_o ies_in_upd_pdr[] = { + M(pdr_id), + O(outer_header_removal), + O(pdi), + O(far_id), + O(activate_predefined_rules), + {0} +}; + +static const struct osmo_tlv_gen_ie upd_pdr = { + "upd_pdr", + .nested_ies = ies_in_upd_pdr, + .spec_ref = "7.5.4.2", +}; + +static const struct osmo_tlv_gen_ie_o ies_in_updated_pdr[] = { + M(pdr_id), + O(local_f_teid), + {0} +}; + +static const struct osmo_tlv_gen_ie updated_pdr = { + "updated_pdr", + .nested_ies = ies_in_updated_pdr, + .spec_ref = "7.5.9.3", +}; + +static const struct osmo_tlv_gen_ie_o ies_in_remove_pdr[] = { + M(pdr_id), + {0} +}; + +static const struct osmo_tlv_gen_ie remove_pdr = { + "remove_pdr", + .nested_ies = ies_in_remove_pdr, + .spec_ref = "7.5.4.6", +}; + +static const struct osmo_tlv_gen_ie apply_action = { + "apply_action", + .decoded_type = "struct osmo_pfcp_ie_apply_action", + .spec_ref = "8.2.26", +}; + +static const struct osmo_tlv_gen_ie destination_iface = { + "destination_iface", + .decoded_type = "enum osmo_pfcp_dest_iface", + .dec_enc = "dest_iface", + .spec_ref = "8.2.24", +}; + +static const struct osmo_tlv_gen_ie destination_iface_type = { + "destination_iface_type", + .decoded_type = "enum osmo_pfcp_3gpp_iface_type", + .tag_name = "3gpp_iface_type", + .spec_ref = "8.2.118", +}; + +static const struct osmo_tlv_gen_ie network_inst = { + "network_inst", + .spec_ref = "8.2.4", +}; + +static const struct osmo_tlv_gen_ie outer_header_creation = { + "outer_header_creation", + .spec_ref = "8.2.56", +}; + +static const struct osmo_tlv_gen_ie linked_te_id = { + "linked_te_id", + .tag_name = "traffic_endpoint_id", + .decoded_type = "uint8_t", + .dec_enc = "8", + .spec_ref = "8.2.92", +}; + +static const struct osmo_tlv_gen_ie_o ies_in_forw_params[] = { + M(destination_iface), + O(network_inst), + O(outer_header_creation), + O(linked_te_id), + O(destination_iface_type), + {0} +}; + +static const struct osmo_tlv_gen_ie forw_params = { + "forw_params", + .nested_ies = ies_in_forw_params, + .spec_ref = "7.5.2.3-2", +}; + +static const struct osmo_tlv_gen_ie_o ies_in_upd_forw_params[] = { + O(destination_iface), + O(network_inst), + O(outer_header_creation), + O(linked_te_id), + O(destination_iface_type), + {0} +}; + +static const struct osmo_tlv_gen_ie upd_forw_params = { + "upd_forw_params", + .nested_ies = ies_in_upd_forw_params, + .spec_ref = "7.5.4.3-2", +}; + +static const struct osmo_tlv_gen_ie_o ies_in_create_far[] = { + M(far_id), + M(apply_action), + O(forw_params), + {0} +}; + +static const struct osmo_tlv_gen_ie create_far = { + "create_far", + .nested_ies = ies_in_create_far, + .spec_ref = "7.5.2.3", +}; + +static const struct osmo_tlv_gen_ie_o ies_in_remove_far[] = { + M(far_id), + {0} +}; + +static const struct osmo_tlv_gen_ie remove_far = { + "remove_far", + .nested_ies = ies_in_remove_far, + .spec_ref = "7.5.4.6", +}; + +static const struct osmo_tlv_gen_ie_o ies_in_upd_far[] = { + M(far_id), + O(apply_action), + O(upd_forw_params), + {0} +}; + +static const struct osmo_tlv_gen_ie upd_far = { + "upd_far", + .nested_ies = ies_in_upd_far, + .spec_ref = "7.5.4.3", +}; + +static const struct osmo_tlv_gen_ie_o ies_in_msg_heartbeat_req[] = { + M(recovery_time_stamp), + {0} +}; + +static const struct osmo_tlv_gen_ie_o ies_in_msg_heartbeat_resp[] = { + M(recovery_time_stamp), + {0} +}; + +static const struct osmo_tlv_gen_ie_o ies_in_msg_assoc_setup_req[] = { + M(node_id), + M(recovery_time_stamp), + O(up_function_features), + O(cp_function_features), + {0} +}; + +static const struct osmo_tlv_gen_ie_o ies_in_msg_assoc_setup_resp[] = { + M(node_id), + M(cause), + M(recovery_time_stamp), + O(up_function_features), + O(cp_function_features), + {0} +}; + +static const struct osmo_tlv_gen_ie_o ies_in_msg_assoc_release_req[] = { + M(node_id), + {0} +}; + +static const struct osmo_tlv_gen_ie_o ies_in_msg_assoc_release_resp[] = { + M(node_id), + M(cause), + {0} +}; + +static const struct osmo_tlv_gen_ie_o ies_in_msg_session_est_req[] = { + M(node_id), + O(cp_f_seid), + MULTI(32, create_pdr), + MULTI(32, create_far), + {0} +}; + +static const struct osmo_tlv_gen_ie_o ies_in_msg_session_est_resp[] = { + M(node_id), + M(cause), + O(offending_ie), + O(up_f_seid), + MULTI(32, created_pdr), + {0} +}; + +static const struct osmo_tlv_gen_ie_o ies_in_msg_session_mod_req[] = { + O(cp_f_seid), + MULTI(32, remove_pdr), + MULTI(32, remove_far), + MULTI(32, create_pdr), + MULTI(32, create_far), + MULTI(32, upd_pdr), + MULTI(32, upd_far), + {0} +}; + +static const struct osmo_tlv_gen_ie_o ies_in_msg_session_mod_resp[] = { + M(cause), + O(offending_ie), + MULTI(32, created_pdr), + MULTI(32, updated_pdr), + {0} +}; + +static const struct osmo_tlv_gen_ie_o ies_in_msg_session_del_req[] = { + /* no IEs */ + {0} +}; + +static const struct osmo_tlv_gen_ie_o ies_in_msg_session_del_resp[] = { + M(cause), + {0} +}; + +static const struct osmo_tlv_gen_msg pfcp_msg_defs[] = { + { "heartbeat_req", ies_in_msg_heartbeat_req }, + { "heartbeat_resp", ies_in_msg_heartbeat_resp }, + { "assoc_setup_req", ies_in_msg_assoc_setup_req }, + { "assoc_setup_resp", ies_in_msg_assoc_setup_resp }, + { "assoc_release_req", ies_in_msg_assoc_release_req }, + { "assoc_release_resp", ies_in_msg_assoc_release_resp }, + { "session_est_req", ies_in_msg_session_est_req }, + { "session_est_resp", ies_in_msg_session_est_resp }, + { "session_mod_req", ies_in_msg_session_mod_req }, + { "session_mod_resp", ies_in_msg_session_mod_resp }, + { "session_del_req", ies_in_msg_session_del_req }, + { "session_del_resp", ies_in_msg_session_del_resp }, + {0} +}; + +int main(int argc, const char **argv) +{ + struct osmo_tlv_gen_cfg cfg = { + .proto_name = "osmo_pfcp", + .spec_ref_prefix = "3GPP TS 29.244 ", + .message_type_enum = "enum osmo_pfcp_message_type", + .message_type_prefix = "OSMO_PFCP_MSGT_", + .tag_enum = "enum osmo_pfcp_iei", + .tag_prefix = "OSMO_PFCP_IEI_", + .decoded_type_prefix = "struct osmo_pfcp_ie_", + .h_header = "#include <osmocom/pfcp/pfcp_ies_custom.h>", + .c_header = "#include <osmocom/pfcp/pfcp_ies_auto.h>", + .msg_defs = pfcp_msg_defs, + }; + return osmo_tlv_gen_main(&cfg, argc, argv); +} diff --git a/src/libosmo-pfcp/pfcp_ies_custom.c b/src/libosmo-pfcp/pfcp_ies_custom.c new file mode 100644 index 0000000..b7c7116 --- /dev/null +++ b/src/libosmo-pfcp/pfcp_ies_custom.c @@ -0,0 +1,729 @@ +/* Decoded PFCP IEs, to be used by the auto-generated pfcp_ies_auto.c. + * + * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH info@sysmocom.de + * All Rights Reserved. + * + * Author: Neels Janosch Hofmeyr nhofmeyr@sysmocom.de + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include <errno.h> + +#include <osmocom/tlv/tlv.h> +#include <osmocom/pfcp/pfcp_msg.h> + +/* Assumes presence of local variable osmo_pfcp_msg *m. m->log_ctx may be NULL. */ +#define RETURN_ERROR(RC, FMT, ARGS...) \ + do {\ + OSMO_ASSERT(decoded_struct); \ + OSMO_LOG_PFCP_MSG((struct osmo_pfcp_msg*)decoded_struct, LOGL_ERROR, FMT " (%d: %s)\n", ##ARGS, RC, \ + strerror((RC) > 0? (RC) : -(RC))); \ + return RC; \ + } while (0) + +/* Assumes presence of local variable osmo_tlv_load *tlv. Usage: + * ENSURE_LENGTH_IS(> 1); + */ +#define ENSURE_LENGTH_IS(CONDITION) \ + if (!(tlv->len CONDITION)) \ + RETURN_ERROR(-EINVAL, "IE has length = %zu, expected length " #CONDITION, tlv->len); + +/* Assumes presence of local variable osmo_tlv_load *tlv. Usage: + * const uint8_t *pos = tlv->val; + * ENSURE_REMAINING_LENGTH_IS("first part", pos, >= 23); + * <parse first part> + * pos += 23; + * ENSURE_REMAINING_LENGTH_IS("very long part", pos, >= 235); + * <parse very long part> + * pos += 235; + */ +#define ENSURE_REMAINING_LENGTH_IS(NAME, POS, CONDITION) \ + if (!((tlv->len - ((POS) - tlv->val)) CONDITION)) \ + RETURN_ERROR(-EINVAL, \ + "at value octet %d: %zu octets remaining, but " #NAME " requires length " #CONDITION, \ + (int)((POS) - tlv->val), \ + tlv->len - ((POS) - tlv->val)); + +void osmo_pfcp_ie_f_seid_set(struct osmo_pfcp_ie_f_seid *f_seid, uint64_t seid, const struct osmo_sockaddr *remote_addr) +{ + *f_seid = (struct osmo_pfcp_ie_f_seid) { + .seid = seid, + }; + osmo_pfcp_ip_addrs_set(&f_seid->ip_addr, remote_addr); +} + +int osmo_pfcp_dec_cause(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv) +{ + enum osmo_pfcp_cause *cause = decode_to; + ENSURE_LENGTH_IS(== 1); + *cause = *tlv->val; + return 0; +} + +int osmo_pfcp_enc_cause(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from) +{ + const enum osmo_pfcp_cause *cause = encode_from; + msgb_put_u8(tlv->dst, *cause); + return 0; +} + +int osmo_pfcp_dec_offending_ie(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv) +{ + enum osmo_pfcp_iei *offending_ie = decode_to; + ENSURE_LENGTH_IS(== 2); + *offending_ie = osmo_load16be(tlv->val); + return 0; +} + +int osmo_pfcp_enc_offending_ie(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from) +{ + const enum osmo_pfcp_iei *offending_ie = encode_from; + msgb_put_u16(tlv->dst, *offending_ie); + return 0; +} + +int osmo_pfcp_dec_8(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv) +{ + uint8_t *u8 = decode_to; + ENSURE_LENGTH_IS(>= 1); + *u8 = tlv->val[0]; + return 0; +} + +int osmo_pfcp_enc_8(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from) +{ + uint8_t *u8 = encode_from; + msgb_put_u8(tlv->dst, *u8); + return 0; +} + +int osmo_pfcp_dec_16be(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv) +{ + uint16_t *u16 = decode_to; + ENSURE_LENGTH_IS(>= 2); + *u16 = osmo_load16be(tlv->val); + return 0; +} + +int osmo_pfcp_enc_16be(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from) +{ + uint16_t *u16 = encode_from; + msgb_put_u16(tlv->dst, *u16); + return 0; +} + +int osmo_pfcp_dec_32be(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv) +{ + uint32_t *u32 = decode_to; + ENSURE_LENGTH_IS(>= 4); + *u32 = osmo_load32be(tlv->val); + return 0; +} + +int osmo_pfcp_enc_32be(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from) +{ + uint32_t *u32 = encode_from; + msgb_put_u32(tlv->dst, *u32); + return 0; +} + +int osmo_pfcp_dec_3gpp_iface_type(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv) +{ + enum osmo_pfcp_3gpp_iface_type *_3gpp_iface_type = decode_to; + ENSURE_LENGTH_IS(>= 1); + *_3gpp_iface_type = tlv->val[0] & 0x3f; + return 0; +} + +int osmo_pfcp_enc_3gpp_iface_type(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from) +{ + enum osmo_pfcp_3gpp_iface_type *_3gpp_iface_type = encode_from; + msgb_put_u8(tlv->dst, (uint8_t)(*_3gpp_iface_type) & 0x3f); + return 0; +} + +int osmo_pfcp_dec_source_iface(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv) +{ + enum osmo_pfcp_source_iface *source_interface = decode_to; + ENSURE_LENGTH_IS(>= 1); + *source_interface = tlv->val[0] & 0xf; + return 0; +} + +int osmo_pfcp_enc_source_iface(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from) +{ + enum osmo_pfcp_source_iface *source_interface = encode_from; + msgb_put_u8(tlv->dst, (uint8_t)(*source_interface) & 0xf); + return 0; +} + +int osmo_pfcp_dec_dest_iface(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv) +{ + enum osmo_pfcp_dest_iface *dest_interface = decode_to; + ENSURE_LENGTH_IS(>= 1); + *dest_interface = tlv->val[0] & 0xf; + return 0; +} + +int osmo_pfcp_enc_dest_iface(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from) +{ + enum osmo_pfcp_dest_iface *dest_interface = encode_from; + msgb_put_u8(tlv->dst, (uint8_t)(*dest_interface) & 0xf); + return 0; +} + +int osmo_pfcp_dec_node_id(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv) +{ + struct osmo_pfcp_ie_node_id *node_id = decode_to; + const void *ip; + unsigned int ip_len; + unsigned int want_len; + ENSURE_LENGTH_IS(>= 1); + node_id->type = *(uint8_t*)tlv->val; + ip = &tlv->val[1]; + ip_len = tlv->len - 1; + + switch (node_id->type) { + case OSMO_PFCP_NODE_ID_T_IPV4: + want_len = sizeof(node_id->ip.u.sin.sin_addr); + if (ip_len != want_len) + RETURN_ERROR(-EINVAL, "Node ID: wrong IPv4 address value length %u, expected %u", + ip_len, want_len); + osmo_sockaddr_from_octets(&node_id->ip, ip, ip_len); + break; + case OSMO_PFCP_NODE_ID_T_IPV6: + want_len = sizeof(node_id->ip.u.sin6.sin6_addr); + if (ip_len != want_len) + RETURN_ERROR(-EINVAL, "Node ID: wrong IPv6 address value length %u, expected %u", + ip_len, want_len); + osmo_sockaddr_from_octets(&node_id->ip, ip, ip_len); + break; + case OSMO_PFCP_NODE_ID_T_FQDN: + /* Copy and add a trailing nul */ + osmo_strlcpy(node_id->fqdn, ip, ip_len); + break; + default: + RETURN_ERROR(-EINVAL, "Invalid Node ID Type: %d", node_id->type); + } + return 0; +} + +int osmo_pfcp_enc_node_id(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from) +{ + unsigned int l; + struct osmo_pfcp_ie_node_id *node_id = encode_from; + msgb_put_u8(tlv->dst, node_id->type); + switch (node_id->type) { + case OSMO_PFCP_NODE_ID_T_IPV4: + l = sizeof(node_id->ip.u.sin.sin_addr); + osmo_sockaddr_to_octets(msgb_put(tlv->dst, l), l, &node_id->ip); + break; + case OSMO_PFCP_NODE_ID_T_IPV6: + l = sizeof(node_id->ip.u.sin6.sin6_addr); + osmo_sockaddr_to_octets(msgb_put(tlv->dst, l), l, &node_id->ip); + break; + case OSMO_PFCP_NODE_ID_T_FQDN: + l = strlen(node_id->fqdn); + /* Copy without trailing nul */ + strncpy((char*)msgb_put(tlv->dst, l), node_id->fqdn, l); + break; + default: + RETURN_ERROR(-EINVAL, "Invalid Node ID Type: %d", node_id->type); + } + return 0; +} + +int osmo_pfcp_dec_up_function_features(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv) +{ + struct osmo_pfcp_ie_up_function_features *up_function_features = decode_to; + ENSURE_LENGTH_IS(>= 6); + memcpy(up_function_features->bits, tlv->val, 6); + return 0; +} + +int osmo_pfcp_enc_up_function_features(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from) +{ + struct osmo_pfcp_ie_up_function_features *up_function_features = encode_from; + memcpy(msgb_put(tlv->dst, 6), up_function_features->bits, 6); + return 0; +} + +int osmo_pfcp_dec_cp_function_features(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv) +{ + struct osmo_pfcp_ie_cp_function_features *cp_function_features = decode_to; + ENSURE_LENGTH_IS(>= sizeof(cp_function_features->bits)); + memcpy(cp_function_features->bits, tlv->val, sizeof(cp_function_features->bits)); + return 0; +} + +int osmo_pfcp_enc_cp_function_features(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from) +{ + struct osmo_pfcp_ie_cp_function_features *cp_function_features = encode_from; + memcpy(msgb_put(tlv->dst, sizeof(cp_function_features->bits)), + cp_function_features->bits, sizeof(cp_function_features->bits)); + return 0; +} + +int osmo_pfcp_dec_f_seid(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv) +{ + struct osmo_pfcp_ie_f_seid *f_seid = decode_to; + uint8_t flags; + uint8_t pos; + unsigned int l; + /* flags and 8 octet seid */ + ENSURE_LENGTH_IS(>= 9); + flags = tlv->val[0]; + f_seid->ip_addr.v6_present = flags & 1; + f_seid->ip_addr.v4_present = flags & 2; + f_seid->seid = osmo_load64be(&tlv->val[1]); + pos = 9; + if (f_seid->ip_addr.v4_present) { + l = sizeof(f_seid->ip_addr.v4.u.sin.sin_addr); + if (pos + l > tlv->len) + RETURN_ERROR(-EINVAL, "F-SEID IE is too short for the IPv4 address: %zu", tlv->len); + osmo_sockaddr_from_octets(&f_seid->ip_addr.v4, &tlv->val[pos], l); + pos += l; + } + if (f_seid->ip_addr.v6_present) { + l = sizeof(f_seid->ip_addr.v4.u.sin6.sin6_addr); + if (pos + l > tlv->len) + RETURN_ERROR(-EINVAL, "F-SEID IE is too short for the IPv6 address: %zu", tlv->len); + osmo_sockaddr_from_octets(&f_seid->ip_addr.v6, &tlv->val[pos], l); + pos += l; + } + return 0; +} + +int osmo_pfcp_enc_f_seid(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from) +{ + struct osmo_pfcp_ie_f_seid *f_seid = encode_from; + unsigned int l; + uint8_t flags = (f_seid->ip_addr.v6_present? 1 : 0) + (f_seid->ip_addr.v4_present ? 2 : 0); + /* flags and 8 octet seid */ + msgb_put_u8(tlv->dst, flags); + osmo_store64be(f_seid->seid, msgb_put(tlv->dst, 8)); + + if (f_seid->ip_addr.v4_present) { + if (f_seid->ip_addr.v4.u.sin.sin_family != AF_INET) + RETURN_ERROR(-EINVAL, + "f_seid IE indicates IPv4 address, but there is no ipv4_addr"); + l = sizeof(f_seid->ip_addr.v4.u.sin.sin_addr); + osmo_sockaddr_to_octets(msgb_put(tlv->dst, l), l, &f_seid->ip_addr.v4); + } + if (f_seid->ip_addr.v6_present) { + if (f_seid->ip_addr.v6.u.sin6.sin6_family != AF_INET6) + RETURN_ERROR(-EINVAL, + "f_seid IE indicates IPv6 address, but there is no ipv6_addr"); + l = sizeof(f_seid->ip_addr.v6.u.sin6.sin6_addr); + osmo_sockaddr_to_octets(msgb_put(tlv->dst, l), l, &f_seid->ip_addr.v6); + } + return 0; +} + +int osmo_pfcp_dec_f_teid(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv) +{ + struct osmo_pfcp_ie_f_teid *f_teid = decode_to; + uint8_t flags; + const uint8_t *pos; + + *f_teid = (struct osmo_pfcp_ie_f_teid){0}; + + pos = tlv->val; + + ENSURE_REMAINING_LENGTH_IS("flags", pos, >= 1); + flags = *pos; + pos++; + f_teid->choose_flag = flags & 4; + + if (!f_teid->choose_flag) { + /* A fixed TEID and address are provided */ + f_teid->fixed.ip_addr.v4_present = flags & 1; + f_teid->fixed.ip_addr.v6_present = flags & 2; + + ENSURE_REMAINING_LENGTH_IS("TEID", pos, >= 4); + f_teid->fixed.teid = osmo_load32be(pos); + pos += 4; + + if (f_teid->fixed.ip_addr.v4_present) { + osmo_static_assert(sizeof(f_teid->fixed.ip_addr.v4.u.sin.sin_addr) == 4, sin_addr_size_is_4); + ENSURE_REMAINING_LENGTH_IS("IPv4 address", pos, >= 4); + osmo_sockaddr_from_octets(&f_teid->fixed.ip_addr.v4, pos, 4); + pos += 4; + } + if (f_teid->fixed.ip_addr.v6_present) { + osmo_static_assert(sizeof(f_teid->fixed.ip_addr.v6.u.sin6.sin6_addr) == 16, sin6_addr_size_is_16); + ENSURE_REMAINING_LENGTH_IS("IPv6 address", pos, >= 16); + osmo_sockaddr_from_octets(&f_teid->fixed.ip_addr.v6, pos, 16); + pos += 16; + } + } else { + /* CH flag is 1, choose an F-TEID. */ + f_teid->choose.ipv4_addr = flags & 1; + f_teid->choose.ipv6_addr = flags & 2; + f_teid->choose.choose_id_present = flags & 8; + + if (f_teid->choose.choose_id_present) { + ENSURE_REMAINING_LENGTH_IS("CHOOSE ID", pos, >= 1); + f_teid->choose.choose_id = *pos; + pos++; + } + } + return 0; +} + +int osmo_pfcp_enc_f_teid(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from) +{ + struct osmo_pfcp_ie_f_teid *f_teid = encode_from; + uint8_t flags; + + flags = (f_teid->choose_flag ? 4 : 0); + + if (!f_teid->choose_flag) { + /* A fixed TEID and address are provided */ + flags |= (f_teid->fixed.ip_addr.v4_present ? 1 : 0) + + (f_teid->fixed.ip_addr.v6_present ? 2 : 0); + + msgb_put_u8(tlv->dst, flags); + msgb_put_u32(tlv->dst, f_teid->fixed.teid); + + if (f_teid->fixed.ip_addr.v4_present) { + if (f_teid->fixed.ip_addr.v4.u.sin.sin_family != AF_INET) + RETURN_ERROR(-EINVAL, + "f_teid IE indicates IPv4 address, but there is no ipv4_addr" + " (sin_family = %d != AF_INET)", f_teid->fixed.ip_addr.v4.u.sin.sin_family); + osmo_sockaddr_to_octets(msgb_put(tlv->dst, 4), 4, &f_teid->fixed.ip_addr.v4); + } + if (f_teid->fixed.ip_addr.v6_present) { + if (f_teid->fixed.ip_addr.v6.u.sin6.sin6_family != AF_INET6) + RETURN_ERROR(-EINVAL, + "f_teid IE indicates IPv6 address, but there is no ipv6_addr" + " (sin6_family = %d != AF_INET6)", f_teid->fixed.ip_addr.v6.u.sin6.sin6_family); + osmo_sockaddr_to_octets(msgb_put(tlv->dst, 16), 16, &f_teid->fixed.ip_addr.v6); + } + } else { + flags |= (f_teid->choose.ipv4_addr ? 1 : 0) + + (f_teid->choose.ipv6_addr ? 2 : 0) + + (f_teid->choose.choose_id_present ? 8 : 0); + msgb_put_u8(tlv->dst, flags); + if (f_teid->choose.choose_id_present) + msgb_put_u8(tlv->dst, f_teid->choose.choose_id); + } + return 0; +} + +int osmo_pfcp_dec_apply_action(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv) +{ + struct osmo_pfcp_ie_apply_action *apply_action = decode_to; + ENSURE_LENGTH_IS(>= 1); + *apply_action = (struct osmo_pfcp_ie_apply_action){0}; + memcpy(apply_action->bits, tlv->val, OSMO_MIN(tlv->len, sizeof(apply_action->bits))); + return 0; +} + +int osmo_pfcp_enc_apply_action(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from) +{ + struct osmo_pfcp_ie_apply_action *apply_action = encode_from; + memcpy(msgb_put(tlv->dst, sizeof(apply_action->bits)), + apply_action->bits, sizeof(apply_action->bits)); + return 0; +} + +int osmo_pfcp_dec_network_inst(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv) +{ + struct osmo_pfcp_ie_network_inst *network_inst = decode_to; + osmo_strlcpy(network_inst->str, (const char *)tlv->val, OSMO_MIN(sizeof(network_inst->str), tlv->len+1)); + return 0; +} + +int osmo_pfcp_enc_network_inst(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from) +{ + struct osmo_pfcp_ie_network_inst *network_inst = encode_from; + unsigned int l = strlen(network_inst->str); + if (l) + memcpy(msgb_put(tlv->dst, l), network_inst->str, l); + return 0; +} + +int osmo_pfcp_dec_outer_header_creation(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv) +{ + struct osmo_pfcp_ie_outer_header_creation *ohc = decode_to; + const uint8_t *pos; + bool gtp_u_udp_ipv4; + bool gtp_u_udp_ipv6; + bool udp_ipv4; + bool udp_ipv6; + bool ipv4; + bool ipv6; + bool c_tag; + bool s_tag; + + *ohc = (struct osmo_pfcp_ie_outer_header_creation){0}; + + ENSURE_LENGTH_IS(>= 2); + + memcpy(ohc->desc_bits, tlv->val, 2); + + gtp_u_udp_ipv4 = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV4); + udp_ipv4 = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_UDP_IPV4); + ipv4 = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_IPV4); + gtp_u_udp_ipv6 = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV6); + udp_ipv6 = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_UDP_IPV6); + ipv6 = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_IPV6); + c_tag = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_C_TAG); + s_tag = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_S_TAG); + + pos = tlv->val + 2; + if (gtp_u_udp_ipv4 || gtp_u_udp_ipv6) { + ENSURE_REMAINING_LENGTH_IS("TEID", pos, >= 4); + ohc->teid_present = true; + ohc->teid = osmo_load32be(pos); + pos += 4; + } + if (gtp_u_udp_ipv4 || udp_ipv4 || ipv4) { + ENSURE_REMAINING_LENGTH_IS("IPv4 address", pos, >= 4); + ohc->ip_addr.v4_present = true; + osmo_sockaddr_from_octets(&ohc->ip_addr.v4, pos, 4); + pos += 4; + } + if (gtp_u_udp_ipv6 || udp_ipv6 || ipv6) { + ENSURE_REMAINING_LENGTH_IS("IPv6 address", pos, >= 16); + ohc->ip_addr.v6_present = true; + osmo_sockaddr_from_octets(&ohc->ip_addr.v6, pos, 16); + pos += 16; + } + if (udp_ipv4 || udp_ipv6) { + ENSURE_REMAINING_LENGTH_IS("UDP port number", pos, >= 2); + ohc->port_number_present = true; + ohc->port_number = osmo_load16be(pos); + pos += 2; + } + if (c_tag) { + ohc->c_tag_present = true; + ENSURE_REMAINING_LENGTH_IS("C-TAG", pos, >= 3); + ohc->c_tag_present = true; + ohc->c_tag = osmo_load32be_ext_2(pos, 3); + pos += 3; + } + if (s_tag) { + ohc->s_tag_present = true; + ENSURE_REMAINING_LENGTH_IS("S-TAG", pos, >= 3); + ohc->s_tag_present = true; + ohc->s_tag = osmo_load32be_ext_2(pos, 3); + pos += 3; + } + + return 0; +} + +int osmo_pfcp_enc_outer_header_creation(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from) +{ + struct osmo_pfcp_ie_outer_header_creation *ohc = encode_from; + bool gtp_u_udp_ipv4; + bool gtp_u_udp_ipv6; + bool udp_ipv4; + bool udp_ipv6; + bool ipv4; + bool ipv6; + bool c_tag; + bool s_tag; + + memcpy(msgb_put(tlv->dst, sizeof(ohc->desc_bits)), ohc->desc_bits, sizeof(ohc->desc_bits)); + + gtp_u_udp_ipv4 = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV4); + udp_ipv4 = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_UDP_IPV4); + ipv4 = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_IPV4); + gtp_u_udp_ipv6 = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV6); + udp_ipv6 = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_UDP_IPV6); + ipv6 = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_IPV6); + c_tag = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_C_TAG); + s_tag = osmo_pfcp_bits_get(ohc->desc_bits, OSMO_PFCP_OUTER_HEADER_CREATION_S_TAG); + + if ((gtp_u_udp_ipv4 || gtp_u_udp_ipv6) != (ohc->teid_present)) + RETURN_ERROR(-EINVAL, "teid_present = %s does not match the description bits 0x%02x\n", + ohc->teid_present ? "true" : "false", + ohc->desc_bits[0]); + if (ohc->teid_present) + msgb_put_u32(tlv->dst, ohc->teid); + + if ((gtp_u_udp_ipv4 || udp_ipv4 || ipv4) != ohc->ip_addr.v4_present) + RETURN_ERROR(-EINVAL, "ipv4_addr_present = %s does not match the description bits 0x%02x\n", + ohc->ip_addr.v4_present ? "true" : "false", + ohc->desc_bits[0]); + if (ohc->ip_addr.v4_present) + osmo_sockaddr_to_octets(msgb_put(tlv->dst, 4), 4, &ohc->ip_addr.v4); + + if ((gtp_u_udp_ipv6 || udp_ipv6 || ipv6) != ohc->ip_addr.v6_present) + RETURN_ERROR(-EINVAL, "ipv6_addr_present = %s does not match the description bits 0x%02x\n", + ohc->ip_addr.v6_present ? "true" : "false", + ohc->desc_bits[0]); + if (ohc->ip_addr.v6_present) + osmo_sockaddr_to_octets(msgb_put(tlv->dst, 16), 16, &ohc->ip_addr.v6); + + if ((udp_ipv4 || udp_ipv6) != ohc->port_number_present) + RETURN_ERROR(-EINVAL, "port_number_present = %s does not match the description bits 0x%02x\n", + ohc->port_number_present ? "true" : "false", + ohc->desc_bits[0]); + if (ohc->port_number_present) + msgb_put_u16(tlv->dst, ohc->port_number); + + if (c_tag != ohc->c_tag_present) + RETURN_ERROR(-EINVAL, "c_tag_present = %s does not match the description bits 0x%02x%02x\n", + ohc->c_tag_present ? "true" : "false", + ohc->desc_bits[1], ohc->desc_bits[0]); + if (ohc->c_tag_present) + osmo_store32be_ext(ohc->c_tag, msgb_put(tlv->dst, 3), 3); + + if (s_tag != ohc->s_tag_present) + RETURN_ERROR(-EINVAL, "s_tag_present = %s does not match the description bits 0x%02x%02x\n", + ohc->s_tag_present ? "true" : "false", + ohc->desc_bits[1], ohc->desc_bits[0]); + if (ohc->s_tag_present) + osmo_store32be_ext(ohc->s_tag, msgb_put(tlv->dst, 3), 3); + + return 0; +} + +int osmo_pfcp_dec_activate_predefined_rules(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv) +{ + struct osmo_pfcp_ie_activate_predefined_rules *activate_predefined_rules = decode_to; + osmo_strlcpy(activate_predefined_rules->str, (const char *)tlv->val, OSMO_MIN(sizeof(activate_predefined_rules->str), tlv->len+1)); + return 0; +} + +int osmo_pfcp_enc_activate_predefined_rules(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from) +{ + struct osmo_pfcp_ie_activate_predefined_rules *activate_predefined_rules = encode_from; + unsigned int l = strlen(activate_predefined_rules->str); + if (l) + memcpy(msgb_put(tlv->dst, l), activate_predefined_rules->str, l); + return 0; +} + +int osmo_pfcp_dec_outer_header_removal(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv) +{ + struct osmo_pfcp_ie_outer_header_removal *outer_header_removal = decode_to; + ENSURE_LENGTH_IS(>= 1); + outer_header_removal->desc = tlv->val[0]; + + if (tlv->len > 1) { + outer_header_removal->gtp_u_extension_header_del_present = true; + memcpy(outer_header_removal->gtp_u_extension_header_del_bits, &tlv->val[1], + sizeof(outer_header_removal->gtp_u_extension_header_del_bits)); + } + return 0; +} + +int osmo_pfcp_enc_outer_header_removal(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from) +{ + struct osmo_pfcp_ie_outer_header_removal *outer_header_removal = encode_from; + msgb_put_u8(tlv->dst, outer_header_removal->desc); + if (outer_header_removal->gtp_u_extension_header_del_present) { + memcpy(msgb_put(tlv->dst, sizeof(outer_header_removal->gtp_u_extension_header_del_bits)), + outer_header_removal->gtp_u_extension_header_del_bits, + sizeof(outer_header_removal->gtp_u_extension_header_del_bits)); + } + return 0; +} + +int osmo_pfcp_dec_ue_ip_address(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv) +{ + struct osmo_pfcp_ie_ue_ip_address *ue_ip_address = decode_to; + const uint8_t *pos; + uint8_t flags; + + pos = tlv->val; + + ENSURE_REMAINING_LENGTH_IS("flags", pos, >= 1); + flags = *pos; + pos++; + + ue_ip_address->ipv6_prefix_length_present = flags & (1 << 6); + ue_ip_address->chv6 = flags & (1 << 5); + ue_ip_address->chv4 = flags & (1 << 4); + ue_ip_address->ipv6_prefix_delegation_bits_present = flags & (1 << 3); + ue_ip_address->ip_is_destination = flags & (1 << 2); + ue_ip_address->ip_addr.v4_present = flags & (1 << 1); + ue_ip_address->ip_addr.v6_present = flags & (1 << 0); + + if (ue_ip_address->ip_addr.v4_present) { + ENSURE_REMAINING_LENGTH_IS("IPv4 address", pos, >= 4); + osmo_sockaddr_from_octets(&ue_ip_address->ip_addr.v4, pos, 4); + pos += 4; + } + if (ue_ip_address->ip_addr.v6_present) { + ENSURE_REMAINING_LENGTH_IS("IPv6 address", pos, >= 16); + osmo_sockaddr_from_octets(&ue_ip_address->ip_addr.v6, pos, 16); + pos += 16; + } + + if (ue_ip_address->ipv6_prefix_delegation_bits_present) { + ENSURE_REMAINING_LENGTH_IS("IPv6 prefix delegation bits", pos, >= 1); + ue_ip_address->ipv6_prefix_delegation_bits = *pos; + pos++; + } + if (ue_ip_address->ipv6_prefix_length_present) { + ENSURE_REMAINING_LENGTH_IS("IPv6 prefix length", pos, >= 1); + ue_ip_address->ipv6_prefix_length = *pos; + pos++; + } + + return 0; +} + +int osmo_pfcp_enc_ue_ip_address(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from) +{ + struct osmo_pfcp_ie_ue_ip_address *ue_ip_address = encode_from; + unsigned int l; + uint8_t flags; + + flags = 0 + | (ue_ip_address->ipv6_prefix_length_present ? (1 << 6) : 0) + | (ue_ip_address->chv6 ? (1 << 5) : 0) + | (ue_ip_address->chv4 ? (1 << 4) : 0) + | (ue_ip_address->ipv6_prefix_delegation_bits_present ? (1 << 3) : 0) + | (ue_ip_address->ip_is_destination ? (1 << 2) : 0) + | (ue_ip_address->ip_addr.v4_present ? (1 << 1) : 0) + | (ue_ip_address->ip_addr.v6_present ? (1 << 0) : 0) + ; + + msgb_put_u8(tlv->dst, flags); + + if (ue_ip_address->ip_addr.v4_present) { + if (ue_ip_address->ip_addr.v4.u.sin.sin_family != AF_INET) + RETURN_ERROR(-EINVAL, + "ue_ip_address IE indicates IPv4 address, but there is no ipv4_addr"); + l = sizeof(ue_ip_address->ip_addr.v4.u.sin.sin_addr); + osmo_sockaddr_to_octets(msgb_put(tlv->dst, l), l, &ue_ip_address->ip_addr.v4); + } + if (ue_ip_address->ip_addr.v6_present) { + if (ue_ip_address->ip_addr.v6.u.sin6.sin6_family != AF_INET6) + RETURN_ERROR(-EINVAL, + "ue_ip_address IE indicates IPv6 address, but there is no ipv6_addr"); + l = sizeof(ue_ip_address->ip_addr.v6.u.sin6.sin6_addr); + osmo_sockaddr_to_octets(msgb_put(tlv->dst, l), l, &ue_ip_address->ip_addr.v6); + } + + if (ue_ip_address->ipv6_prefix_delegation_bits_present) + msgb_put_u8(tlv->dst, ue_ip_address->ipv6_prefix_delegation_bits); + + if (ue_ip_address->ipv6_prefix_length_present) + msgb_put_u8(tlv->dst, ue_ip_address->ipv6_prefix_length); + + return 0; +}