import various generic IPA related functions from libosmo-abis

libosmo-abis is about forming A-bis interfaces/lines by means
of E1 or the IPA multiplex (or possibly other link layers).

The IPA multiplex is used in other contexts, such as the Control
interface, or the A interface.  In that context, it makes sense to
have generic IPA related functions in libosmocore.
diff --git a/include/Makefile.am b/include/Makefile.am
index 3046758..3b35b7b 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -58,6 +58,7 @@
                        osmocom/gsm/gsm48.h \
                        osmocom/gsm/gsm48_ie.h \
                        osmocom/gsm/gsm_utils.h \
+                       osmocom/gsm/ipaccess.h \
                        osmocom/gsm/lapd_core.h \
                        osmocom/gsm/lapdm.h \
                        osmocom/gsm/meas_rep.h \
diff --git a/include/osmocom/gsm/ipaccess.h b/include/osmocom/gsm/ipaccess.h
new file mode 100644
index 0000000..dd56069
--- /dev/null
+++ b/include/osmocom/gsm/ipaccess.h
@@ -0,0 +1,62 @@
+#pragma once
+
+#include <stdint.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/tlv.h>
+
+/* internal (host-only) data structure */
+struct ipaccess_unit {
+	uint16_t site_id;
+	uint16_t bts_id;
+	uint16_t trx_id;
+	char *unit_name;
+	char *equipvers;
+	char *swversion;
+	uint8_t mac_addr[6];
+	char *location1;
+	char *location2;
+	char *serno;
+};
+
+/* obtain the human-readable name of an IPA CCM ID TAG */
+const char *ipaccess_idtag_name(uint8_t tag);
+
+/* parse a buffer of ID tags into a osmocom TLV style representation */
+int ipaccess_idtag_parse(struct tlv_parsed *dec, unsigned char *buf, int len);
+
+/* parse an Unit ID in string format into the 'ipaccess_unit' data structure */
+int ipaccess_parse_unitid(const char *str, struct ipaccess_unit *unit_data);
+
+/* fill a 'struct ipaccess_unit' based on a parsed IDTAG TLV */
+int ipaccess_tlv_to_unitdata(struct ipaccess_unit *ud,
+			     const struct tlv_parsed *tp);
+
+/* Send an IPA message to the given FD */
+int ipaccess_send(int fd, const void *msg, size_t msglen);
+
+/* Send an IPA CCM PONG via the given FD */
+int ipaccess_send_pong(int fd);
+
+/* Send an IPA CCM ID_ACK via the given FD */
+int ipaccess_send_id_ack(int fd);
+
+/* Send an IPA CCM ID_REQ via the given FD */
+int ipaccess_send_id_req(int fd);
+
+/* Common handling of IPA CCM, BSC side */
+int ipaccess_rcvmsg_base(struct msgb *msg, struct osmo_fd *bfd);
+
+/* Common handling of IPA CCM, BTS side */
+int ipaccess_rcvmsg_bts_base(struct msgb *msg, struct osmo_fd *bfd);
+
+/* prepend (push) an ipaccess_head_ext to the msgb */
+void ipaccess_prepend_header_ext(struct msgb *msg, int proto);
+
+/* prepend (push) an ipaccess_head to the msgb */
+void ipaccess_prepend_header(struct msgb *msg, int proto);
+
+struct msgb *ipa_msg_alloc(int headroom);
+
+int ipa_msg_recv(int fd, struct msgb **rmsg);
+int ipa_msg_recv_buffered(int fd, struct msgb **rmsg, struct msgb **tmp_msg);
diff --git a/src/gsm/Makefile.am b/src/gsm/Makefile.am
index 94729c9..06b1f18 100644
--- a/src/gsm/Makefile.am
+++ b/src/gsm/Makefile.am
@@ -19,7 +19,7 @@
 			auth_core.c auth_comp128v1.c auth_comp128v23.c \
 			auth_milenage.c milenage/aes-encblock.c \
 			milenage/aes-internal.c milenage/aes-internal-enc.c \
-			milenage/milenage.c gan.c
+			milenage/milenage.c gan.c ipaccess.c
 
 libosmogsm_la_LDFLAGS = $(LTLDFLAGS_OSMOGSM) -version-info $(LIBVERSION) -no-undefined
 libosmogsm_la_LIBADD = $(top_builddir)/src/libosmocore.la
diff --git a/src/gsm/ipaccess.c b/src/gsm/ipaccess.c
new file mode 100644
index 0000000..cddbd53
--- /dev/null
+++ b/src/gsm/ipaccess.c
@@ -0,0 +1,447 @@
+/* OpenBSC Abis input driver for ip.access */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by Holger Hans Peter Freyther
+ * (C) 2010 by On-Waves
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <unistd.h>
+#include <stdint.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/macaddr.h>
+#include <osmocom/core/select.h>
+
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gsm/protocol/ipaccess.h>
+#include <osmocom/gsm/ipaccess.h>
+
+#define IPA_ALLOC_SIZE 1200
+
+/*
+ * Common propietary IPA messages:
+ *      - PONG: in reply to PING.
+ *      - ID_REQUEST: first messages once OML has been established.
+ *      - ID_ACK: in reply to ID_ACK.
+ */
+static const uint8_t ipa_pong_msg[] = {
+	0, 1, IPAC_PROTO_IPACCESS, IPAC_MSGT_PONG
+};
+
+static const uint8_t ipa_id_ack_msg[] = {
+	0, 1, IPAC_PROTO_IPACCESS, IPAC_MSGT_ID_ACK
+};
+
+static const uint8_t ipa_id_req_msg[] = {
+	0, 17, IPAC_PROTO_IPACCESS, IPAC_MSGT_ID_GET,
+	0x01, IPAC_IDTAG_UNIT,
+	0x01, IPAC_IDTAG_MACADDR,
+	0x01, IPAC_IDTAG_LOCATION1,
+	0x01, IPAC_IDTAG_LOCATION2,
+	0x01, IPAC_IDTAG_EQUIPVERS,
+	0x01, IPAC_IDTAG_SWVERSION,
+	0x01, IPAC_IDTAG_UNITNAME,
+	0x01, IPAC_IDTAG_SERNR,
+};
+
+
+static const char *idtag_names[] = {
+	[IPAC_IDTAG_SERNR]	= "Serial_Number",
+	[IPAC_IDTAG_UNITNAME]	= "Unit_Name",
+	[IPAC_IDTAG_LOCATION1]	= "Location_1",
+	[IPAC_IDTAG_LOCATION2]	= "Location_2",
+	[IPAC_IDTAG_EQUIPVERS]	= "Equipment_Version",
+	[IPAC_IDTAG_SWVERSION]	= "Software_Version",
+	[IPAC_IDTAG_IPADDR]	= "IP_Address",
+	[IPAC_IDTAG_MACADDR]	= "MAC_Address",
+	[IPAC_IDTAG_UNIT]	= "Unit_ID",
+};
+
+const char *ipaccess_idtag_name(uint8_t tag)
+{
+	if (tag >= ARRAY_SIZE(idtag_names))
+		return "unknown";
+
+	return idtag_names[tag];
+}
+
+int ipaccess_idtag_parse(struct tlv_parsed *dec, unsigned char *buf, int len)
+{
+	uint8_t t_len;
+	uint8_t t_tag;
+	uint8_t *cur = buf;
+
+	memset(dec, 0, sizeof(*dec));
+
+	while (len >= 2) {
+		len -= 2;
+		t_len = *cur++;
+		t_tag = *cur++;
+
+		if (t_len > len + 1) {
+			LOGP(DLMI, LOGL_ERROR, "The tag does not fit: %d\n", t_len);
+			return -EINVAL;
+		}
+
+		DEBUGPC(DLMI, "%s='%s' ", ipaccess_idtag_name(t_tag), cur);
+
+		dec->lv[t_tag].len = t_len;
+		dec->lv[t_tag].val = cur;
+
+		cur += t_len;
+		len -= t_len;
+	}
+	return 0;
+}
+
+int ipaccess_parse_unitid(const char *str, struct ipaccess_unit *unit_data)
+{
+	unsigned long ul;
+	char *endptr;
+	const char *nptr;
+
+	nptr = str;
+	ul = strtoul(nptr, &endptr, 10);
+	if (endptr <= nptr)
+		return -EINVAL;
+	unit_data->site_id = ul & 0xffff;
+
+	if (*endptr++ != '/')
+		return -EINVAL;
+
+	nptr = endptr;
+	ul = strtoul(nptr, &endptr, 10);
+	if (endptr <= nptr)
+		return -EINVAL;
+	unit_data->bts_id = ul & 0xffff;
+
+	if (*endptr++ != '/')
+		return -EINVAL;
+
+	nptr = endptr;
+	ul = strtoul(nptr, &endptr, 10);
+	if (endptr <= nptr)
+		return -EINVAL;
+	unit_data->trx_id = ul & 0xffff;
+
+	return 0;
+}
+
+int ipaccess_tlv_to_unitdata(struct ipaccess_unit *ud,
+			     const struct tlv_parsed *tp)
+{
+	int rc = 0;
+
+	if (TLVP_PRES_LEN(tp, IPAC_IDTAG_SERNR, 1))
+		ud->serno = talloc_strdup(ud, (char *)
+					TLVP_VAL(tp, IPAC_IDTAG_SERNR));
+
+	if (TLVP_PRES_LEN(tp, IPAC_IDTAG_UNITNAME, 1))
+		ud->unit_name = talloc_strdup(ud, (char *)
+					TLVP_VAL(tp, IPAC_IDTAG_UNITNAME));
+
+	if (TLVP_PRES_LEN(tp, IPAC_IDTAG_LOCATION1, 1))
+		ud->location1 = talloc_strdup(ud, (char *)
+					TLVP_VAL(tp, IPAC_IDTAG_LOCATION1));
+
+	if (TLVP_PRES_LEN(tp, IPAC_IDTAG_LOCATION2, 1))
+		ud->location2 = talloc_strdup(ud, (char *)
+					TLVP_VAL(tp, IPAC_IDTAG_LOCATION2));
+
+	if (TLVP_PRES_LEN(tp, IPAC_IDTAG_EQUIPVERS, 1))
+		ud->equipvers = talloc_strdup(ud, (char *)
+					TLVP_VAL(tp, IPAC_IDTAG_EQUIPVERS));
+
+	if (TLVP_PRES_LEN(tp, IPAC_IDTAG_SWVERSION, 1))
+		ud->swversion = talloc_strdup(ud, (char *)
+					TLVP_VAL(tp, IPAC_IDTAG_SWVERSION));
+
+	if (TLVP_PRES_LEN(tp, IPAC_IDTAG_MACADDR, 17)) {
+		rc = osmo_macaddr_parse(ud->mac_addr, (char *)
+					TLVP_VAL(tp, IPAC_IDTAG_MACADDR));
+		if (rc < 0)
+			goto out;
+	}
+
+	if (TLVP_PRES_LEN(tp, IPAC_IDTAG_UNIT, 1))
+		rc = ipaccess_parse_unitid((char *)
+					TLVP_VAL(tp, IPAC_IDTAG_UNIT), ud);
+
+out:
+	return rc;
+}
+
+int ipaccess_send(int fd, const void *msg, size_t msglen)
+{
+	int ret;
+
+	ret = write(fd, msg, msglen);
+	if (ret < 0)
+		return ret;
+	if (ret < msglen) {
+		LOGP(DLINP, LOGL_ERROR, "ipaccess_send: short write\n");
+		return -EIO;
+	}
+	return ret;
+}
+
+int ipaccess_send_pong(int fd)
+{
+	return ipaccess_send(fd, ipa_pong_msg, sizeof(ipa_pong_msg));
+}
+
+int ipaccess_send_id_ack(int fd)
+{
+	return ipaccess_send(fd, ipa_id_ack_msg, sizeof(ipa_id_ack_msg));
+}
+
+int ipaccess_send_id_req(int fd)
+{
+	return ipaccess_send(fd, ipa_id_req_msg, sizeof(ipa_id_req_msg));
+}
+
+/* base handling of the ip.access protocol */
+int ipaccess_rcvmsg_base(struct msgb *msg, struct osmo_fd *bfd)
+{
+	uint8_t msg_type = *(msg->l2h);
+	int ret;
+
+	switch (msg_type) {
+	case IPAC_MSGT_PING:
+		ret = ipaccess_send_pong(bfd->fd);
+		if (ret < 0) {
+			LOGP(DLINP, LOGL_ERROR, "Cannot send PING "
+			     "message. Reason: %s\n", strerror(errno));
+			break;
+		}
+		ret = 1;
+		break;
+	case IPAC_MSGT_PONG:
+		DEBUGP(DLMI, "PONG!\n");
+		ret = 1;
+		break;
+	case IPAC_MSGT_ID_ACK:
+		DEBUGP(DLMI, "ID_ACK? -> ACK!\n");
+		ret = ipaccess_send_id_ack(bfd->fd);
+		if (ret < 0) {
+			LOGP(DLINP, LOGL_ERROR, "Cannot send ID_ACK "
+			     "message. Reason: %s\n", strerror(errno));
+			break;
+		}
+		ret = 1;
+		break;
+	default:
+		/* This is not an IPA PING, PONG or ID_ACK message */
+		ret = 0;
+		break;
+	}
+	return ret;
+}
+
+/* base handling of the ip.access protocol */
+int ipaccess_rcvmsg_bts_base(struct msgb *msg,
+			     struct osmo_fd *bfd)
+{
+	uint8_t msg_type = *(msg->l2h);
+	int ret = 0;
+
+	switch (msg_type) {
+	case IPAC_MSGT_PING:
+		ret = ipaccess_send_pong(bfd->fd);
+		if (ret < 0) {
+			LOGP(DLINP, LOGL_ERROR, "Cannot send PONG "
+			     "message. Reason: %s\n", strerror(errno));
+		}
+		break;
+	case IPAC_MSGT_PONG:
+		DEBUGP(DLMI, "PONG!\n");
+		break;
+	case IPAC_MSGT_ID_ACK:
+		DEBUGP(DLMI, "ID_ACK\n");
+		break;
+	}
+	return ret;
+}
+
+
+void ipaccess_prepend_header_ext(struct msgb *msg, int proto)
+{
+	struct ipaccess_head_ext *hh_ext;
+
+	/* prepend the osmo ip.access header extension */
+	hh_ext = (struct ipaccess_head_ext *) msgb_push(msg, sizeof(*hh_ext));
+	hh_ext->proto = proto;
+}
+
+void ipaccess_prepend_header(struct msgb *msg, int proto)
+{
+	struct ipaccess_head *hh;
+
+	/* prepend the ip.access header */
+	hh = (struct ipaccess_head *) msgb_push(msg, sizeof(*hh));
+	hh->len = htons(msg->len - sizeof(*hh));
+	hh->proto = proto;
+}
+
+int ipa_msg_recv(int fd, struct msgb **rmsg)
+{
+	int rc = ipa_msg_recv_buffered(fd, rmsg, NULL);
+	if (rc < 0) {
+		errno = -rc;
+		rc = -1;
+	}
+	return rc;
+}
+
+int ipa_msg_recv_buffered(int fd, struct msgb **rmsg, struct msgb **tmp_msg)
+{
+	struct msgb *msg = tmp_msg ? *tmp_msg : NULL;
+	struct ipaccess_head *hh;
+	int len, ret;
+	int needed;
+
+	if (msg == NULL) {
+		msg = ipa_msg_alloc(0);
+		if (msg == NULL) {
+			ret = -ENOMEM;
+			goto discard_msg;
+		}
+		msg->l1h = msg->tail;
+	}
+
+	if (msg->l2h == NULL) {
+		/* first read our 3-byte header */
+		needed = sizeof(*hh) - msg->len;
+		ret = recv(fd, msg->tail, needed, 0);
+		if (ret == 0)
+		       goto discard_msg;
+
+		if (ret < 0) {
+			if (errno == EAGAIN || errno == EINTR)
+				ret = 0;
+			else {
+				ret = -errno;
+				goto discard_msg;
+			}
+		}
+
+		msgb_put(msg, ret);
+
+		if (ret < needed) {
+			if (msg->len == 0) {
+				ret = -EAGAIN;
+				goto discard_msg;
+			}
+
+			LOGP(DLINP, LOGL_INFO,
+			     "Received part of IPA message header (%d/%d)\n",
+			     msg->len, sizeof(*hh));
+			if (!tmp_msg) {
+				ret = -EIO;
+				goto discard_msg;
+			}
+			*tmp_msg = msg;
+			return -EAGAIN;
+		}
+
+		msg->l2h = msg->tail;
+	}
+
+	hh = (struct ipaccess_head *) msg->data;
+
+	/* then read the length as specified in header */
+	len = ntohs(hh->len);
+
+	if (len < 0 || IPA_ALLOC_SIZE < len + sizeof(*hh)) {
+		LOGP(DLINP, LOGL_ERROR, "bad message length of %d bytes, "
+					"received %d bytes\n", len, msg->len);
+		ret = -EIO;
+		goto discard_msg;
+	}
+
+	needed = len - msgb_l2len(msg);
+
+	if (needed > 0) {
+		ret = recv(fd, msg->tail, needed, 0);
+
+		if (ret == 0)
+			goto discard_msg;
+
+		if (ret < 0) {
+			if (errno == EAGAIN || errno == EINTR)
+				ret = 0;
+			else {
+				ret = -errno;
+				goto discard_msg;
+			}
+		}
+
+		msgb_put(msg, ret);
+
+		if (ret < needed) {
+			LOGP(DLINP, LOGL_INFO,
+			     "Received part of IPA message L2 data (%d/%d)\n",
+			    msgb_l2len(msg), len);
+			if (!tmp_msg) {
+				ret = -EIO;
+				goto discard_msg;
+			}
+			*tmp_msg = msg;
+			return -EAGAIN;
+		}
+	}
+
+	ret = msgb_l2len(msg);
+
+	if (ret == 0) {
+		LOGP(DLINP, LOGL_INFO,
+		     "Discarding IPA message without payload\n");
+		ret = -EAGAIN;
+		goto discard_msg;
+	}
+
+	if (tmp_msg)
+		*tmp_msg = NULL;
+	*rmsg = msg;
+	return ret;
+
+discard_msg:
+	if (tmp_msg)
+		*tmp_msg = NULL;
+	msgb_free(msg);
+	return ret;
+}
+
+struct msgb *ipa_msg_alloc(int headroom)
+{
+	struct msgb *nmsg;
+
+	headroom += sizeof(struct ipaccess_head);
+
+	nmsg = msgb_alloc_headroom(1200 + headroom, headroom, "Abis/IP");
+	if (!nmsg)
+		return NULL;
+	return nmsg;
+}