From 2100097ef1be98a338b583f15bb5f529690829ff Mon Sep 17 00:00:00 2001 From: Neels Hofmeyr Date: Wed, 12 Jan 2022 02:38:39 +0100 Subject: libosmo-gtlv: add generic TLV de- and encoder An all new TLV parser supporting: - Any size of T and L (determined by callback function), - "Grouped IEs", so that an IE payload is a nested IE structure, - optional/mandatory/multi-occurence IEs, - decoding unordered tags (or enforcing strict order). Will be used for PFCP message decoding and encoding, a T16L16V protocol which requires above features. Upcoming patches add - translating PDUs to plain C structs and vice versa - TLV generator to reduce repetition a in protocol definition - TLIV capability Previously, the way we deal with TLVs causes a lot of code re-implementation: the TL decoding is taken care of by the API, but for encoding, we essentially re-implement each protocol and each encoded message in the individual programs. This API is an improvement in that we only once implement the TL coding (or just use osmo_t8l8v_cfg / osmo_t16l16v_cfg), get symmetric de- and encoding of the TL, and only need to deal with the value part of each IE. The common pattern of - store TL preliminarily, - write V data and - update L after V is complete is conveniently done by osmo_gtlv_put_update_tl(). Related: SYS#5599 Change-Id: Ib0fd00d9f288ffe13b7e67701f3e47073587404a --- include/osmocom/Makefile.am | 1 + include/osmocom/gtlv/Makefile.am | 5 ++ include/osmocom/gtlv/gtlv.h | 137 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 include/osmocom/gtlv/Makefile.am create mode 100644 include/osmocom/gtlv/gtlv.h (limited to 'include') diff --git a/include/osmocom/Makefile.am b/include/osmocom/Makefile.am index 0800830..79ca2df 100644 --- a/include/osmocom/Makefile.am +++ b/include/osmocom/Makefile.am @@ -1,3 +1,4 @@ SUBDIRS = \ + gtlv \ pfcp \ $(NULL) diff --git a/include/osmocom/gtlv/Makefile.am b/include/osmocom/gtlv/Makefile.am new file mode 100644 index 0000000..f922ab8 --- /dev/null +++ b/include/osmocom/gtlv/Makefile.am @@ -0,0 +1,5 @@ +tlv_HEADERS = \ + gtlv.h \ + $(NULL) + +tlvdir = $(includedir)/osmocom/gtlv diff --git a/include/osmocom/gtlv/gtlv.h b/include/osmocom/gtlv/gtlv.h new file mode 100644 index 0000000..7acf558 --- /dev/null +++ b/include/osmocom/gtlv/gtlv.h @@ -0,0 +1,137 @@ +/* + * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH + * All Rights Reserved. + * + * Author: Neels Janosch Hofmeyr + * + * 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 . + * + */ + +#pragma once + +#include +#include + +struct msgb; +struct osmo_gtlv_load; +struct osmo_gtlv_put; + +/*! TL configuration for osmo_gtlv_load*() and osmo_gtlv_put*(). Depending on these implementations provided by the caller, + * osmo_gtlv can load any sizes of tag and length fields (that don't surpass the value range of unsigned int and size_t, + * respectively), as well as TV (fixed-length) or TvLV (variable-sized length). + * + * See osmo_t8l8v_cfg and osmo_t16l16v_cfg, ready implementations for plain 8bit and 16bit TLV protocols. + * + * libosmo-pfcp serves as example for using this entire TLV API, uncluding de/encoding to structs and generating parts + * of the TLV parsing code based on message definitions. It uses osmo_t16l16v_cfg. + */ +struct osmo_gtlv_cfg { + /*! The length in bytes of the shortest possible TL header (e.g. 4 for T16L16V, or 1 for 8bit tags where TV IEs + * without a length exist). A src_data_len passed to store_tl() below is guaranteed to be >= this value. If at + * any point there is remaining message data smaller than this value, a parsing error is returned. + */ + size_t tl_min_size; + + /*! Read one TL from the start of src_data. + * \param gtlv Return the T (tag) value read from src_data in gtlv->tag. + * Return the L (length) value read from src_data in gtlv->len. + * Return the position just after the TL in gtlv->*val. If there is V data, point at the start of the + * V data in src_data. If there is no V data, point at the byte just after the TL part in src_data. + * \param src_data Part of raw message being decoded. + * \param src_data_len Remaining message data length at src_data. + * \return 0 on success, negative on error. + */ + int (*load_tl)(struct osmo_gtlv_load *gtlv, const uint8_t *src_data, size_t src_data_len); + + /*! Write a TL to dst_data, and return the size of the TL written. + * This is also invoked by osmo_gtlv_put_update_tl() to overwrite a previous TL header. If the TL part's size + * can be different than the first time (e.g. due to a large L value in a TvLV protocol), an implementation can + * use the 'gtlv' arg to figure out how to memmove the message data: + * When invoked by osmo_gtlv_put_tl(), dst_data == gtlv->dst->tail and dst_data_avail == msgb_tailroom(). + * When invoked by osmo_gtlv_put_update_tl(), dst_data < gtlv->dst->tail, dst_data points at the start of the + * TL section written earlier by osmo_gtlv_put_tl() and dst_data_avail == the size of the TL written earlier. + * + * \param dst_data Write TL data to the start of this buffer. + * \param dst_data_avail Remaining available space in dst_data. + * \param tag The T value to store in dst_data. + * \param len The L value to store in dst_data. + * \param gtlv Backpointer to the osmo_gtlv_put struct, including gtlv->dst, the underlying msgb. + * \return the size of the TL part in bytes on success, -EINVAL if tag is invalid, -EMSGSIZE if len is too large + * or dst_data_avail is too small for the TL. + */ + int (*store_tl)(uint8_t *dst_data, size_t dst_data_avail, unsigned int tag, size_t len, struct osmo_gtlv_put *gtlv); +}; + +/*! Configuration that allows parsing an 8bit tag and 8bit length TLV. */ +extern const struct osmo_gtlv_cfg osmo_t8l8v_cfg; + +/*! Configuration that allows parsing a 16bit tag and 16bit length TLV (see for example PFCP). */ +extern const struct osmo_gtlv_cfg osmo_t16l16v_cfg; + +/*! State for loading a TLV structure from raw data. */ +struct osmo_gtlv_load { + /*! Caller-defined context pointer available for use by load_tl() and store_tl() implementations. */ + void *priv; + + /*! Definition of tag and length sizes (by function pointers). */ + const struct osmo_gtlv_cfg *cfg; + + /*! Overall message buffer being parsed. */ + struct { + const uint8_t *data; + size_t len; + } src; + + /*! Return value from last invocation of osmo_gtlv_load_next*(): tag value of parsed IE. */ + unsigned int tag; + /*! Return value from last invocation of osmo_gtlv_load_next*(): Start of the IE's payload data (after tag and + * length). If the end of the src buffer is reached, val == NULL. If a TLV contained no value part, len == 0, + * but this still points just after the TL. */ + const uint8_t *val; + /*! Return value from last invocation of osmo_gtlv_load_next*(): Length of the IE's payload data (without tag and + * length) */ + size_t len; +}; + +/* Start or restart the gtlv from the first IE in the overall TLV data. */ +static inline void osmo_gtlv_load_start(struct osmo_gtlv_load *gtlv) +{ + gtlv->val = NULL; +} + +int osmo_gtlv_load_next(struct osmo_gtlv_load *gtlv); +int osmo_gtlv_load_peek_tag(const struct osmo_gtlv_load *gtlv); +int osmo_gtlv_load_next_by_tag(struct osmo_gtlv_load *gtlv, unsigned int tag); + +/* State for storing a TLV structure into a msgb. */ +struct osmo_gtlv_put { + /*! Caller-defined context pointer available for use by load_tl() and store_tl() implementations. */ + void *priv; + + /* Definition of tag and length sizes (by function pointers). */ + const struct osmo_gtlv_cfg *cfg; + + /* msgb to append new TL to */ + struct msgb *dst; + /* What was the last TL written and where are its TL and V */ + unsigned int last_tag; + uint8_t *last_tl; + uint8_t *last_val; +}; + +int osmo_gtlv_put_tl(struct osmo_gtlv_put *gtlv, unsigned int tag, size_t len); +int osmo_gtlv_put_update_tl(struct osmo_gtlv_put *gtlv); -- cgit v1.2.3