aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNeels Hofmeyr <nhofmeyr@sysmocom.de>2022-01-12 03:17:55 +0100
committerNeels Hofmeyr <neels@hofmeyr.de>2022-06-16 13:04:33 +0200
commitb53c785d7806f580100cf1628b08aa081d57af3d (patch)
tree77a8079b810f8340cb0fa6181841bc7dc905e9e7
parent778071cc99789172b3e6d80a42582abd65a8502a (diff)
libosmo-pfcp: implement/generate TLV and IE value coding
-rw-r--r--include/osmocom/pfcp/Makefile.am16
-rw-r--r--include/osmocom/pfcp/pfcp_ies_custom.h163
-rw-r--r--src/libosmo-pfcp/Makefile.am32
-rw-r--r--src/libosmo-pfcp/gen__pfcp_ies_auto.c365
-rw-r--r--src/libosmo-pfcp/pfcp_ies_custom.c989
5 files changed, 1565 insertions, 0 deletions
diff --git a/include/osmocom/pfcp/Makefile.am b/include/osmocom/pfcp/Makefile.am
index ff7df5e..4bfc194 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-gtlv/gtlv_gen.c \
+ $(top_srcdir)/include/osmocom/gtlv/gtlv_gen.h
+ $(MAKE) -C $(top_builddir)/src/libosmo-gtlv
+ $(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..9f26550
--- /dev/null
+++ b/include/osmocom/pfcp/pfcp_ies_custom.h
@@ -0,0 +1,163 @@
+/* Definitions for 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#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];
+ };
+};
+
+bool osmo_pfcp_bits_get(const uint8_t *bits, unsigned int bitpos);
+void osmo_pfcp_bits_set(uint8_t *bits, unsigned int bitpos, bool val);
+int osmo_pfcp_bits_to_str_buf(char *buf, size_t buflen, const uint8_t *bits, const struct value_string *bit_strs);
+char *osmo_pfcp_bits_to_str_c(void *ctx, const uint8_t *bits, const struct value_string *bit_str);
+
+/* 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..0e249f9 100644
--- a/src/libosmo-pfcp/Makefile.am
+++ b/src/libosmo-pfcp/Makefile.am
@@ -24,5 +24,37 @@ noinst_LIBRARIES = \
$(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-gtlv/gtlv_gen.c \
+ $(top_srcdir)/include/osmocom/gtlv/gtlv_gen.h
+ $(MAKE) -C $(top_builddir)/src/libosmo-gtlv
+ $(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-gtlv/libosmo-gtlv.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..0dfb43c
--- /dev/null
+++ b/src/libosmo-pfcp/gen__pfcp_ies_auto.c
@@ -0,0 +1,365 @@
+/* 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/gtlv/gtlv_gen.h>
+
+#define O OSMO_GTLV_GEN_O
+#define M OSMO_GTLV_GEN_M
+#define O_MULTI OSMO_GTLV_GEN_O_MULTI
+#define M_MULTI OSMO_GTLV_GEN_M_MULTI
+#define ALL_FROM_NAME osmo_gtlv_gen_ie_auto
+
+#define Ms(MEMB_NAME) M(MEMB_NAME, #MEMB_NAME)
+#define Os(MEMB_NAME) O(MEMB_NAME, #MEMB_NAME)
+#define O_MULTIs(N, MEMB_NAME) O_MULTI(N, MEMB_NAME, #MEMB_NAME)
+#define M_MULTIs(N, M, MEMB_NAME) M_MULTI(N, M, MEMB_NAME, #MEMB_NAME)
+
+static const struct osmo_gtlv_gen_ie recovery_time_stamp = {
+ "uint32_t",
+ .dec_enc = "32be",
+ .spec_ref = "7.4.2",
+};
+
+static const struct osmo_gtlv_gen_ie cause = {
+ "enum osmo_pfcp_cause",
+ .spec_ref = "8.2.1",
+};
+
+static const struct osmo_gtlv_gen_ie offending_ie = {
+ .decoded_type = "enum osmo_pfcp_iei",
+ .spec_ref = "8.2.22",
+};
+
+static const struct osmo_gtlv_gen_ie f_seid = {
+ .tag_name = "f_seid",
+ .spec_ref = "8.2.37",
+};
+
+static const struct osmo_gtlv_gen_ie pdr_id = {
+ .decoded_type = "uint16_t",
+ .dec_enc = "16be",
+ .spec_ref = "8.2.36",
+};
+
+static const struct osmo_gtlv_gen_ie precedence = {
+ .decoded_type = "uint32_t",
+ .dec_enc = "32be",
+ .spec_ref = "8.2.11",
+};
+
+static const struct osmo_gtlv_gen_ie source_iface = {
+ .decoded_type = "enum osmo_pfcp_source_iface",
+ .spec_ref = "8.2.2",
+};
+
+static const struct osmo_gtlv_gen_ie f_teid = {
+ .tag_name = "f_teid",
+ .spec_ref = "8.2.3",
+};
+
+static const struct osmo_gtlv_gen_ie traffic_endpoint_id = {
+ .tag_name = "traffic_endpoint_id",
+ .decoded_type = "uint8_t",
+ .dec_enc = "8",
+ .spec_ref = "8.2.92",
+};
+
+static const struct osmo_gtlv_gen_ie iface_type = {
+ .decoded_type = "enum osmo_pfcp_3gpp_iface_type",
+ .tag_name = "3gpp_iface_type",
+ .spec_ref = "8.2.118",
+};
+
+static const struct osmo_gtlv_gen_ie_o ies_in_pdi[] = {
+ Ms(source_iface),
+ O(f_teid, "local_f_teid"),
+ O(ALL_FROM_NAME, "ue_ip_address"),
+ Os(traffic_endpoint_id),
+ O(iface_type, "source_iface_type"),
+ {}
+};
+
+static const struct osmo_gtlv_gen_ie pdi = {
+ .nested_ies = ies_in_pdi,
+ .spec_ref = "7.5.2.2-2",
+};
+
+static const struct osmo_gtlv_gen_ie far_id = {
+ .decoded_type = "uint32_t",
+ .dec_enc = "32be",
+ .spec_ref = "8.2.74",
+};
+
+static const struct osmo_gtlv_gen_ie_o ies_in_create_pdr[] = {
+ Ms(pdr_id),
+ Ms(precedence),
+ Ms(pdi),
+ O(ALL_FROM_NAME, "outer_header_removal"),
+ Os(far_id),
+ O(ALL_FROM_NAME, "activate_predefined_rules"),
+ {}
+};
+
+static const struct osmo_gtlv_gen_ie create_pdr = {
+ .nested_ies = ies_in_create_pdr,
+ .spec_ref = "7.5.2.2",
+};
+
+static const struct osmo_gtlv_gen_ie_o ies_in_created_pdr[] = {
+ Ms(pdr_id),
+ O(f_teid, "local_f_teid"),
+ {}
+};
+
+static const struct osmo_gtlv_gen_ie created_pdr = {
+ .nested_ies = ies_in_created_pdr,
+ .spec_ref = "7.5.3.2",
+};
+
+static const struct osmo_gtlv_gen_ie_o ies_in_upd_pdr[] = {
+ Ms(pdr_id),
+ O(ALL_FROM_NAME, "outer_header_removal"),
+ Os(pdi),
+ Os(far_id),
+ O(ALL_FROM_NAME, "activate_predefined_rules"),
+ {}
+};
+
+static const struct osmo_gtlv_gen_ie upd_pdr = {
+ .nested_ies = ies_in_upd_pdr,
+ .spec_ref = "7.5.4.2",
+};
+
+static const struct osmo_gtlv_gen_ie_o ies_in_updated_pdr[] = {
+ Ms(pdr_id),
+ O(f_teid, "local_f_teid"),
+ {}
+};
+
+static const struct osmo_gtlv_gen_ie updated_pdr = {
+ .nested_ies = ies_in_updated_pdr,
+ .spec_ref = "7.5.9.3",
+};
+
+static const struct osmo_gtlv_gen_ie_o ies_in_remove_pdr[] = {
+ Ms(pdr_id),
+ {}
+};
+
+static const struct osmo_gtlv_gen_ie remove_pdr = {
+ .nested_ies = ies_in_remove_pdr,
+ .spec_ref = "7.5.4.6",
+};
+
+static const struct osmo_gtlv_gen_ie destination_iface = {
+ .decoded_type = "enum osmo_pfcp_dest_iface",
+ .dec_enc = "dest_iface",
+ .spec_ref = "8.2.24",
+};
+
+static const struct osmo_gtlv_gen_ie_o ies_in_forw_params[] = {
+ Ms(destination_iface),
+ O(ALL_FROM_NAME, "network_inst"),
+ O(ALL_FROM_NAME, "outer_header_creation"),
+ O(traffic_endpoint_id, "linked_te_id"),
+ O(iface_type, "destination_iface_type"),
+ {}
+};
+
+static const struct osmo_gtlv_gen_ie forw_params = {
+ .nested_ies = ies_in_forw_params,
+ .spec_ref = "7.5.2.3-2",
+};
+
+static const struct osmo_gtlv_gen_ie_o ies_in_upd_forw_params[] = {
+ Os(destination_iface),
+ O(ALL_FROM_NAME, "network_inst"),
+ O(ALL_FROM_NAME, "outer_header_creation"),
+ O(traffic_endpoint_id, "linked_te_id"),
+ O(iface_type, "destination_iface_type"),
+ {}
+};
+
+static const struct osmo_gtlv_gen_ie upd_forw_params = {
+ .nested_ies = ies_in_upd_forw_params,
+ .spec_ref = "7.5.4.3-2",
+};
+
+static const struct osmo_gtlv_gen_ie_o ies_in_create_far[] = {
+ Ms(far_id),
+ M(ALL_FROM_NAME, "apply_action"),
+ Os(forw_params),
+ {}
+};
+
+static const struct osmo_gtlv_gen_ie create_far = {
+ .nested_ies = ies_in_create_far,
+ .spec_ref = "7.5.2.3",
+};
+
+static const struct osmo_gtlv_gen_ie_o ies_in_remove_far[] = {
+ Ms(far_id),
+ {}
+};
+
+static const struct osmo_gtlv_gen_ie remove_far = {
+ .nested_ies = ies_in_remove_far,
+ .spec_ref = "7.5.4.6",
+};
+
+static const struct osmo_gtlv_gen_ie_o ies_in_upd_far[] = {
+ Ms(far_id),
+ O(ALL_FROM_NAME, "apply_action"),
+ Os(upd_forw_params),
+ {}
+};
+
+static const struct osmo_gtlv_gen_ie upd_far = {
+ .nested_ies = ies_in_upd_far,
+ .spec_ref = "7.5.4.3",
+};
+
+static const struct osmo_gtlv_gen_ie_o ies_in_msg_heartbeat_req[] = {
+ Ms(recovery_time_stamp),
+ {}
+};
+
+static const struct osmo_gtlv_gen_ie_o ies_in_msg_heartbeat_resp[] = {
+ Ms(recovery_time_stamp),
+ {}
+};
+
+static const struct osmo_gtlv_gen_ie_o ies_in_msg_assoc_setup_req[] = {
+ M(ALL_FROM_NAME, "node_id"),
+ Ms(recovery_time_stamp),
+ O(ALL_FROM_NAME, "up_function_features"),
+ O(ALL_FROM_NAME, "cp_function_features"),
+ {}
+};
+
+static const struct osmo_gtlv_gen_ie_o ies_in_msg_assoc_setup_resp[] = {
+ M(ALL_FROM_NAME, "node_id"),
+ Ms(cause),
+ Ms(recovery_time_stamp),
+ O(ALL_FROM_NAME, "up_function_features"),
+ O(ALL_FROM_NAME, "cp_function_features"),
+ {}
+};
+
+static const struct osmo_gtlv_gen_ie_o ies_in_msg_assoc_release_req[] = {
+ M(ALL_FROM_NAME, "node_id"),
+ {}
+};
+
+static const struct osmo_gtlv_gen_ie_o ies_in_msg_assoc_release_resp[] = {
+ M(ALL_FROM_NAME, "node_id"),
+ Ms(cause),
+ {}
+};
+
+static const struct osmo_gtlv_gen_ie_o ies_in_msg_session_est_req[] = {
+ M(ALL_FROM_NAME, "node_id"),
+ O(f_seid, "cp_f_seid"),
+ M_MULTIs(32, 1, create_pdr),
+ M_MULTI(32, 1, create_far, "create_far"),
+ {}
+};
+
+static const struct osmo_gtlv_gen_ie_o ies_in_msg_session_est_resp[] = {
+ M(ALL_FROM_NAME, "node_id"),
+ Ms(cause),
+ Os(offending_ie),
+ O(f_seid, "up_f_seid"),
+ O_MULTIs(32, created_pdr),
+ {}
+};
+
+static const struct osmo_gtlv_gen_ie_o ies_in_msg_session_mod_req[] = {
+ O(f_seid, "cp_f_seid"),
+ O_MULTIs(32, remove_pdr),
+ O_MULTIs(32, remove_far),
+ O_MULTIs(32, create_pdr),
+ O_MULTIs(32, create_far),
+ O_MULTIs(32, upd_pdr),
+ O_MULTIs(32, upd_far),
+ {}
+};
+
+static const struct osmo_gtlv_gen_ie_o ies_in_msg_session_mod_resp[] = {
+ Ms(cause),
+ Os(offending_ie),
+ O_MULTIs(32, created_pdr),
+ O_MULTIs(32, updated_pdr),
+ {}
+};
+
+static const struct osmo_gtlv_gen_ie_o ies_in_msg_session_del_req[] = {
+ /* no IEs */
+ {}
+};
+
+static const struct osmo_gtlv_gen_ie_o ies_in_msg_session_del_resp[] = {
+ Ms(cause),
+ {}
+};
+
+#define MSG(NAME) { #NAME, ies_in_msg_##NAME }
+static const struct osmo_gtlv_gen_msg pfcp_msg_defs[] = {
+ MSG(heartbeat_req),
+ MSG(heartbeat_resp),
+ MSG(assoc_setup_req),
+ MSG(assoc_setup_resp),
+ MSG(assoc_release_req),
+ MSG(assoc_release_resp),
+ MSG(session_est_req),
+ MSG(session_est_resp),
+ MSG(session_mod_req),
+ MSG(session_mod_resp),
+ MSG(session_del_req),
+ MSG(session_del_resp),
+ {}
+};
+
+int main(int argc, const char **argv)
+{
+ struct osmo_gtlv_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,
+ .add_enc_to_str = true,
+ };
+ return osmo_gtlv_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..660a08d
--- /dev/null
+++ b/src/libosmo-pfcp/pfcp_ies_custom.c
@@ -0,0 +1,989 @@
+/* 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <inttypes.h>
+
+#include <osmocom/core/socket.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+
+#include <osmocom/gtlv/gtlv.h>
+
+#include <osmocom/pfcp/pfcp_ies_custom.h>
+#include <osmocom/pfcp/pfcp_strs.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); \
+ LOGP(DLPFCP, LOGL_ERROR, FMT " (%d: %s)\n", ##ARGS, RC, \
+ strerror((RC) > 0 ? (RC) : -(RC))); \
+ return RC; \
+ } while (0)
+
+/* Assumes presence of local variable osmo_gtlv_load *tlv. Usage:
+ * ENSURE_LENGTH_IS_EXACTLY(2);
+ */
+#define ENSURE_LENGTH_IS_EXACTLY(VAL) \
+ do { \
+ if (!(tlv->len == VAL)) \
+ RETURN_ERROR(-EINVAL, "IE has length = %zu, expected length == " #VAL, tlv->len); \
+ } while (0)
+
+/* Assumes presence of local variable osmo_gtlv_load *tlv. Usage:
+ * ENSURE_LENGTH_IS_AT_LEAST(1);
+ */
+#define ENSURE_LENGTH_IS_AT_LEAST(VAL) \
+ do { \
+ if (!(tlv->len >= VAL)) \
+ RETURN_ERROR(-EINVAL, "IE has length = %zu, expected length >= " #VAL, tlv->len); \
+ } while (0)
+
+/* Assumes presence of local variable osmo_gtlv_load *tlv. Usage:
+ * const uint8_t *pos = tlv->val;
+ * ENSURE_REMAINING_LENGTH_IS_AT_LEAST("first part", pos, 23);
+ * <parse first part>
+ * pos += 23;
+ * ENSURE_REMAINING_LENGTH_IS_AT_LEAST("very long part", pos, 235);
+ * <parse very long part>
+ * pos += 235;
+ */
+#define ENSURE_REMAINING_LENGTH_IS_AT_LEAST(NAME, POS, MIN_VAL) \
+ do { \
+ if (!((tlv->len - ((POS) - tlv->val)) >= MIN_VAL)) \
+ RETURN_ERROR(-EINVAL, \
+ "at value octet %d: %zu octets remaining, but " #NAME " requires length >= " #MIN_VAL, \
+ (int)((POS) - tlv->val), \
+ tlv->len - ((POS) - tlv->val)); \
+ } while (0)
+
+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_gtlv_load *tlv)
+{
+ enum osmo_pfcp_cause *cause = decode_to;
+ ENSURE_LENGTH_IS_EXACTLY(1);
+ *cause = *tlv->val;
+ return 0;
+}
+
+int osmo_pfcp_enc_cause(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
+{
+ const enum osmo_pfcp_cause *cause = encode_from;
+ msgb_put_u8(tlv->dst, *cause);
+ return 0;
+}
+
+int osmo_pfcp_enc_to_str_cause(char *buf, size_t buflen, const void *encode_from)
+{
+ const enum osmo_pfcp_cause *cause = encode_from;
+ return snprintf(buf, buflen, "%s", osmo_pfcp_cause_str(*cause));
+}
+
+int osmo_pfcp_dec_offending_ie(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *tlv)
+{
+ enum osmo_pfcp_iei *offending_ie = decode_to;
+ ENSURE_LENGTH_IS_EXACTLY(2);
+ *offending_ie = osmo_load16be(tlv->val);
+ return 0;
+}
+
+int osmo_pfcp_enc_offending_ie(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
+{
+ const enum osmo_pfcp_iei *offending_ie = encode_from;
+ msgb_put_u16(tlv->dst, *offending_ie);
+ return 0;
+}
+
+int osmo_pfcp_enc_to_str_offending_ie(char *buf, size_t buflen, const void *encode_from)
+{
+ const enum osmo_pfcp_iei *offending_ie = encode_from;
+ return snprintf(buf, buflen, "%s", osmo_pfcp_iei_str(*offending_ie));
+}
+
+int osmo_pfcp_dec_8(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *tlv)
+{
+ uint8_t *u8 = decode_to;
+ ENSURE_LENGTH_IS_AT_LEAST(1);
+ *u8 = tlv->val[0];
+ return 0;
+}
+
+int osmo_pfcp_enc_8(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
+{
+ const uint8_t *u8 = encode_from;
+ msgb_put_u8(tlv->dst, *u8);
+ return 0;
+}
+
+int osmo_pfcp_enc_to_str_8(char *buf, size_t buflen, const void *encode_from)
+{
+ const uint8_t *u8 = encode_from;
+ return snprintf(buf, buflen, "%u", *u8);
+}
+
+int osmo_pfcp_dec_16be(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *tlv)
+{
+ uint16_t *u16 = decode_to;
+ ENSURE_LENGTH_IS_AT_LEAST(2);
+ *u16 = osmo_load16be(tlv->val);
+ return 0;
+}
+
+int osmo_pfcp_enc_16be(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
+{
+ const uint16_t *u16 = encode_from;
+ msgb_put_u16(tlv->dst, *u16);
+ return 0;
+}
+
+int osmo_pfcp_enc_to_str_16be(char *buf, size_t buflen, const void *encode_from)
+{
+ const uint16_t *u16 = encode_from;
+ return snprintf(buf, buflen, "%u", *u16);
+}
+
+int osmo_pfcp_dec_32be(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *tlv)
+{
+ uint32_t *u32 = decode_to;
+ ENSURE_LENGTH_IS_AT_LEAST(4);
+ *u32 = osmo_load32be(tlv->val);
+ return 0;
+}
+
+int osmo_pfcp_enc_32be(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
+{
+ const uint32_t *u32 = encode_from;
+ msgb_put_u32(tlv->dst, *u32);
+ return 0;
+}
+
+int osmo_pfcp_enc_to_str_32be(char *buf, size_t buflen, const void *encode_from)
+{
+ const uint32_t *u32 = encode_from;
+ return snprintf(buf, buflen, "%u", *u32);
+}
+
+int osmo_pfcp_dec_3gpp_iface_type(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *tlv)
+{
+ enum osmo_pfcp_3gpp_iface_type *_3gpp_iface_type = decode_to;
+ ENSURE_LENGTH_IS_AT_LEAST(1);
+ *_3gpp_iface_type = tlv->val[0] & 0x3f;
+ return 0;
+}
+
+int osmo_pfcp_enc_3gpp_iface_type(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
+{
+ const 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_enc_to_str_3gpp_iface_type(char *buf, size_t buflen, const void *encode_from)
+{
+ const enum osmo_pfcp_3gpp_iface_type *_3gpp_iface_type = encode_from;
+ return snprintf(buf, buflen, "%s", osmo_pfcp_3gpp_iface_type_str(*_3gpp_iface_type));
+}
+
+int osmo_pfcp_dec_source_iface(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *tlv)
+{
+ enum osmo_pfcp_source_iface *source_iface = decode_to;
+ ENSURE_LENGTH_IS_AT_LEAST(1);
+ *source_iface = tlv->val[0] & 0xf;
+ return 0;
+}
+
+int osmo_pfcp_enc_source_iface(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
+{
+ const enum osmo_pfcp_source_iface *source_iface = encode_from;
+ msgb_put_u8(tlv->dst, (uint8_t)(*source_iface) & 0xf);
+ return 0;
+}
+
+int osmo_pfcp_enc_to_str_source_iface(char *buf, size_t buflen, const void *encode_from)
+{
+ const enum osmo_pfcp_source_iface *source_iface = encode_from;
+ return snprintf(buf, buflen, "%s", osmo_pfcp_source_iface_str(*source_iface));
+}
+
+int osmo_pfcp_dec_dest_iface(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *tlv)
+{
+ enum osmo_pfcp_dest_iface *dest_interface = decode_to;
+ ENSURE_LENGTH_IS_AT_LEAST(1);
+ *dest_interface = tlv->val[0] & 0xf;
+ return 0;
+}
+
+int osmo_pfcp_enc_dest_iface(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
+{
+ const enum osmo_pfcp_dest_iface *dest_interface = encode_from;
+ msgb_put_u8(tlv->dst, (uint8_t)(*dest_interface) & 0xf);
+ return 0;
+}
+
+int osmo_pfcp_enc_to_str_dest_iface(char *buf, size_t buflen, const void *encode_from)
+{
+ const enum osmo_pfcp_dest_iface *dest_iface = encode_from;
+ return snprintf(buf, buflen, "%s", osmo_pfcp_dest_iface_str(*dest_iface));
+}
+
+int osmo_pfcp_dec_node_id(void *decoded_struct, void *decode_to, const struct osmo_gtlv_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_AT_LEAST(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_ARRAY(node_id->fqdn, ip);
+ break;
+ default:
+ RETURN_ERROR(-EINVAL, "Invalid Node ID Type: %d", node_id->type);
+ }
+ return 0;
+}
+
+int osmo_pfcp_enc_node_id(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
+{
+ unsigned int l;
+ const 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 = strnlen(node_id->fqdn, sizeof(node_id->fqdn));
+ /* Copy without trailing nul */
+ memcpy((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_enc_to_str_node_id(char *buf, size_t buflen, const void *encode_from)
+{
+ const struct osmo_pfcp_ie_node_id *node_id = encode_from;
+ struct osmo_strbuf sb = { .buf = buf, .len = buflen };
+
+ switch (node_id->type) {
+ case OSMO_PFCP_NODE_ID_T_IPV4:
+ OSMO_STRBUF_PRINTF(sb, "v4:");
+ break;
+ case OSMO_PFCP_NODE_ID_T_IPV6:
+ OSMO_STRBUF_PRINTF(sb, "v6:");
+ break;
+ case OSMO_PFCP_NODE_ID_T_FQDN:
+ OSMO_STRBUF_PRINTF(sb, "fqdn:");
+ OSMO_STRBUF_APPEND(sb, osmo_quote_str_buf3,
+ node_id->fqdn, strnlen(node_id->fqdn, sizeof(node_id->fqdn)));
+ return sb.chars_needed;
+ default:
+ OSMO_STRBUF_PRINTF(sb, "unknown-node-id-type-%u", node_id->type);
+ return sb.chars_needed;
+ }
+
+ OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &node_id->ip);
+ return sb.chars_needed;
+}
+
+bool osmo_pfcp_bits_get(const uint8_t *bits, unsigned int bitpos)
+{
+ unsigned int bytenum = bitpos / 8;
+ unsigned int bitmask = 1 << (bitpos % 8);
+
+ return (bool)(bits[bytenum] & bitmask);
+}
+
+void osmo_pfcp_bits_set(uint8_t *bits, unsigned int bitpos, bool val)
+{
+ unsigned int bytenum = bitpos / 8;
+ unsigned int bitmask = 1 << (bitpos % 8);
+
+ if (val)
+ bits[bytenum] |= bitmask;
+ else
+ bits[bytenum] &= ~bitmask;
+}
+
+int osmo_pfcp_bits_to_str_buf(char *buf, size_t buflen, const uint8_t *bits, const struct value_string *bit_strs)
+{
+ struct osmo_strbuf sb = { .buf = buf, .len = buflen };
+ OSMO_STRBUF_PRINTF(sb, "(");
+ for (; bit_strs->str; bit_strs++) {
+ if (osmo_pfcp_bits_get(bits, bit_strs->value)) {
+ OSMO_STRBUF_PRINTF(sb, " %s", bit_strs->str);
+ }
+ }
+ OSMO_STRBUF_PRINTF(sb, " )");
+ return sb.chars_needed;
+}
+
+char *osmo_pfcp_bits_to_str_c(void *ctx, const uint8_t *bits, const struct value_string *bit_str)
+{
+ OSMO_NAME_C_IMPL(ctx, 64, "ERROR", osmo_pfcp_bits_to_str_buf, bits, bit_str)
+}
+
+int osmo_pfcp_dec_up_function_features(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *tlv)
+{
+ struct osmo_pfcp_ie_up_function_features *up_function_features = decode_to;
+ ENSURE_LENGTH_IS_AT_LEAST(6);
+ memcpy(up_function_features->bits, tlv->val, 6);
+ return 0;
+}
+
+int osmo_pfcp_enc_up_function_features(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
+{
+ const 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_enc_to_str_up_function_features(char *buf, size_t buflen, const void *encode_from)
+{
+ const struct osmo_pfcp_ie_up_function_features *up_function_features = encode_from;
+ return osmo_pfcp_bits_to_str_buf(buf, buflen, up_function_features->bits, osmo_pfcp_up_feature_strs);
+}
+
+int osmo_pfcp_dec_cp_function_features(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *tlv)
+{
+ struct osmo_pfcp_ie_cp_function_features *cp_function_features = decode_to;
+ ENSURE_LENGTH_IS_AT_LEAST(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_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
+{
+ const 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_enc_to_str_cp_function_features(char *buf, size_t buflen, const void *encode_from)
+{
+ const struct osmo_pfcp_ie_cp_function_features *cp_function_features = encode_from;
+ return osmo_pfcp_bits_to_str_buf(buf, buflen, cp_function_features->bits, osmo_pfcp_cp_feature_strs);
+}
+
+int osmo_pfcp_dec_f_seid(void *decoded_struct, void *decode_to, const struct osmo_gtlv_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_AT_LEAST(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_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
+{
+ const 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;
+}
+
+static int ip_addrs_to_str_buf(char *buf, size_t buflen, const struct osmo_pfcp_ip_addrs *addrs)
+{
+ struct osmo_strbuf sb = { .buf = buf, .len = buflen };
+ if (addrs->v4_present) {
+ OSMO_STRBUF_PRINTF(sb, ",v4:");
+ OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &addrs->v4);
+ }
+ if (addrs->v6_present) {
+ OSMO_STRBUF_PRINTF(sb, ",v6:");
+ OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &addrs->v6);
+ }
+ return sb.chars_needed;
+}
+
+int osmo_pfcp_enc_to_str_f_seid(char *buf, size_t buflen, const void *encode_from)
+{
+ const struct osmo_pfcp_ie_f_seid *f_seid = encode_from;
+ struct osmo_strbuf sb = { .buf = buf, .len = buflen };
+ OSMO_STRBUF_PRINTF(sb, "0x%"PRIx64, f_seid->seid);
+ OSMO_STRBUF_APPEND(sb, ip_addrs_to_str_buf, &f_seid->ip_addr);
+ return sb.chars_needed;
+}
+
+int osmo_pfcp_dec_f_teid(void *decoded_struct, void *decode_to, const struct osmo_gtlv_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){};
+
+ pos = tlv->val;
+
+ ENSURE_REMAINING_LENGTH_IS_AT_LEAST("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_AT_LEAST("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_AT_LEAST("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_AT_LEAST("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_AT_LEAST("CHOOSE ID", pos, 1);
+ f_teid->choose.choose_id = *pos;
+ pos++;
+ }
+ }
+ return 0;
+}
+
+int osmo_pfcp_enc_f_teid(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
+{
+ const 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_ie_f_teid_to_str_buf(char *buf, size_t buflen, const struct osmo_pfcp_ie_f_teid *ft)
+{
+ struct osmo_strbuf sb = { .buf = buf, .len = buflen };
+ if (ft->choose_flag) {
+ OSMO_STRBUF_PRINTF(sb, "CHOOSE");
+ if (ft->choose.ipv4_addr)
+ OSMO_STRBUF_PRINTF(sb, "-v4");
+ if (ft->choose.ipv6_addr)
+ OSMO_STRBUF_PRINTF(sb, "-v6");
+ if (ft->choose.choose_id_present)
+ OSMO_STRBUF_PRINTF(sb, "-id%u", ft->choose.choose_id);
+ } else {
+ OSMO_STRBUF_PRINTF(sb, "TEID-0x%x", ft->fixed.teid);
+ OSMO_STRBUF_APPEND(sb, ip_addrs_to_str_buf, &ft->fixed.ip_addr);
+ }
+ return sb.chars_needed;
+}
+
+char *osmo_pfcp_ie_f_teid_to_str_c(void *ctx, const struct osmo_pfcp_ie_f_teid *ft)
+{
+ OSMO_NAME_C_IMPL(ctx, 64, "ERROR", osmo_pfcp_ie_f_teid_to_str_buf, ft)
+}
+
+int osmo_pfcp_enc_to_str_f_teid(char *buf, size_t buflen, const void *encode_from)
+{
+ const struct osmo_pfcp_ie_f_teid *f_teid = encode_from;
+ return osmo_pfcp_ie_f_teid_to_str_buf(buf, buflen, f_teid);
+}
+
+int osmo_pfcp_dec_apply_action(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *tlv)
+{
+ struct osmo_pfcp_ie_apply_action *apply_action = decode_to;
+ ENSURE_LENGTH_IS_AT_LEAST(1);
+ *apply_action = (struct osmo_pfcp_ie_apply_action){};
+ memcpy(apply_action->bits, tlv->val, OSMO_MIN(tlv->len, sizeof(apply_action->bits)));
+ return 0;
+}
+
+int osmo_pfcp_enc_apply_action(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
+{
+ const 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_enc_to_str_apply_action(char *buf, size_t buflen, const void *encode_from)
+{
+ const struct osmo_pfcp_ie_apply_action *apply_action = encode_from;
+ return osmo_pfcp_bits_to_str_buf(buf, buflen, apply_action->bits, osmo_pfcp_apply_action_strs);
+}
+
+int osmo_pfcp_dec_network_inst(void *decoded_struct, void *decode_to, const struct osmo_gtlv_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_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
+{
+ const 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_enc_to_str_network_inst(char *buf, size_t buflen, const void *encode_from)
+{
+ const struct osmo_pfcp_ie_network_inst *network_inst = encode_from;
+ return osmo_quote_str_buf3(buf, buflen, network_inst->str,
+ strnlen(network_inst->str, sizeof(network_inst->str)));
+}
+
+int osmo_pfcp_dec_outer_header_creation(void *decoded_struct, void *decode_to, const struct osmo_gtlv_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){};
+
+ ENSURE_LENGTH_IS_AT_LEAST(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_AT_LEAST("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_AT_LEAST("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_AT_LEAST("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_AT_LEAST("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_AT_LEAST("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_AT_LEAST("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_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
+{
+ const 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_enc_to_str_outer_header_creation(char *buf, size_t buflen, const void *encode_from)
+{
+ const struct osmo_pfcp_ie_outer_header_creation *ohc = encode_from;
+ struct osmo_strbuf sb = { .buf = buf, .len = buflen };
+ OSMO_STRBUF_APPEND(sb, osmo_pfcp_bits_to_str_buf, ohc->desc_bits, osmo_pfcp_outer_header_creation_strs);
+ if (ohc->teid_present)
+ OSMO_STRBUF_PRINTF(sb, ",TEID:0x%x", ohc->teid);
+ OSMO_STRBUF_APPEND(sb, ip_addrs_to_str_buf, &ohc->ip_addr);
+ if (ohc->port_number_present)
+ OSMO_STRBUF_PRINTF(sb, ",port:%u", ohc->port_number);
+ if (ohc->c_tag_present)
+ OSMO_STRBUF_PRINTF(sb, ",c-tag:%u", ohc->c_tag);
+ if (ohc->s_tag_present)
+ OSMO_STRBUF_PRINTF(sb, ",s-tag:%u", ohc->s_tag);
+ return sb.chars_needed;
+}
+
+int osmo_pfcp_dec_activate_predefined_rules(void *decoded_struct, void *decode_to, const struct osmo_gtlv_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_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
+{
+ const 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_enc_to_str_activate_predefined_rules(char *buf, size_t buflen, const void *encode_from)
+{
+ const struct osmo_pfcp_ie_activate_predefined_rules *activate_predefined_rules = encode_from;
+ return osmo_quote_str_buf3(buf, buflen, activate_predefined_rules->str,
+ strnlen(activate_predefined_rules->str, sizeof(activate_predefined_rules->str)));
+}
+
+int osmo_pfcp_dec_outer_header_removal(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *tlv)
+{
+ struct osmo_pfcp_ie_outer_header_removal *outer_header_removal = decode_to;
+ ENSURE_LENGTH_IS_AT_LEAST(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_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
+{
+ const 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_enc_to_str_outer_header_removal(char *buf, size_t buflen, const void *encode_from)
+{
+ const struct osmo_pfcp_ie_outer_header_removal *outer_header_removal = encode_from;
+ struct osmo_strbuf sb = { .buf = buf, .len = buflen };
+ OSMO_STRBUF_PRINTF(sb, "%s", osmo_pfcp_outer_header_removal_desc_str(outer_header_removal->desc));
+ if (outer_header_removal->gtp_u_extension_header_del_present)
+ OSMO_STRBUF_PRINTF(sb, ",ext-hdr-del:0x%x", outer_header_removal->gtp_u_extension_header_del_bits[0]);
+ return sb.chars_needed;
+}
+
+int osmo_pfcp_dec_ue_ip_address(void *decoded_struct, void *decode_to, const struct osmo_gtlv_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_AT_LEAST("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_AT_LEAST("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_AT_LEAST("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_AT_LEAST("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_AT_LEAST("IPv6 prefix length", pos, 1);
+ ue_ip_address->ipv6_prefix_length = *pos;
+ pos++;
+ }
+
+ return 0;
+}
+
+int osmo_pfcp_enc_ue_ip_address(struct osmo_gtlv_put *tlv, const void *decoded_struct, const void *encode_from)
+{
+ const 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;
+}
+
+int osmo_pfcp_enc_to_str_ue_ip_address(char *buf, size_t buflen, const void *encode_from)
+{
+ const struct osmo_pfcp_ie_ue_ip_address *uia = encode_from;
+ struct osmo_strbuf sb = { .buf = buf, .len = buflen };
+ if (uia->chv4)
+ OSMO_STRBUF_PRINTF(sb, "chv4");
+ if (uia->chv6)
+ OSMO_STRBUF_PRINTF(sb, "%schv4", sb.pos ? "," : "");
+ if (uia->ip_is_destination)
+ OSMO_STRBUF_PRINTF(sb, "%sdst", sb.pos ? "," : "");
+ OSMO_STRBUF_APPEND(sb, ip_addrs_to_str_buf, &uia->ip_addr);
+ if (uia->ipv6_prefix_delegation_bits_present)
+ OSMO_STRBUF_PRINTF(sb, ",ipv6-prefix-deleg:%x", uia->ipv6_prefix_delegation_bits);
+ if (uia->ipv6_prefix_length_present)
+ OSMO_STRBUF_PRINTF(sb, ",ipv6-prefix-len:%u", uia->ipv6_prefix_length);
+ return sb.chars_needed;
+}