/* (C) 2008-2012 by Harald Welte <laforge@gnumonks.org>
 *
 * All Rights Reserved
 *
 * Author: Harald Welte <laforge@gnumonks.org>
 *         Pablo Neira Ayuso <pablo@gnumonks.org>
 *
 * SPDX-License-Identifier: AGPL-3.0+
 *
 * 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 <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <string.h>
#include <errno.h>

#include <osmocom/core/msgb.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/utils.h>

#include <osmocom/abis/lapd_pcap.h>

/*
 * pcap writing of the mlapd load
 * pcap format is from http://wiki.wireshark.org/Development/LibpcapFileFormat
 */
#define DLT_LINUX_LAPD		177
#define LINUX_SLL_HOST		0
#define LINUX_SLL_OUTGOING	4

struct pcap_hdr {
	uint32_t magic_number;
	uint16_t version_major;
	uint16_t version_minor;
	int32_t  thiszone;
	uint32_t sigfigs;
	uint32_t snaplen;
	uint32_t network;
} __attribute__((packed));

struct pcap_rechdr {
	uint32_t ts_sec;
	uint32_t ts_usec;
	uint32_t incl_len;
	uint32_t orig_len;
} __attribute__((packed));

struct pcap_lapdhdr {
	uint16_t pkttype;
	uint16_t hatype;
	uint16_t halen;
	uint8_t addr[8];
	int16_t protocol;
} __attribute__((packed));

osmo_static_assert(offsetof(struct pcap_lapdhdr, hatype) == 2, hatype_offset);
osmo_static_assert(offsetof(struct pcap_lapdhdr, halen) == 4, halen_offset);
osmo_static_assert(offsetof(struct pcap_lapdhdr, addr) == 6, addr_offset);
osmo_static_assert(offsetof(struct pcap_lapdhdr, protocol) == 14, proto_offset);
osmo_static_assert(sizeof(struct pcap_lapdhdr) == 16, lapd_header_size);

int osmo_pcap_lapd_set_fd(int fd)
{
		struct pcap_hdr pcap_header = {
		.magic_number	= 0xa1b2c3d4,
		.version_major	= 2,
		.version_minor	= 4,
		.thiszone	= 0,
		.sigfigs	= 0,
		.snaplen	= 65535,
		.network	= DLT_LINUX_LAPD,
	};

	if (write(fd, &pcap_header, sizeof(pcap_header))
					!= sizeof(pcap_header)) {
		LOGP(DLLAPD, LOGL_ERROR, "cannot write PCAP header: %s\n",
			strerror(errno));
		close(fd);
		return -1;
	}

	return 0;
}

int osmo_pcap_lapd_open(char *filename, mode_t mode)
{
	int fd, rc;

	LOGP(DLLAPD, LOGL_NOTICE, "opening LAPD pcap file `%s'\n", filename);

	fd = open(filename, O_WRONLY|O_TRUNC|O_CREAT, mode);
	if (fd < 0) {
		LOGP(DLLAPD, LOGL_ERROR, "failed to open PCAP file: %s\n",
			strerror(errno));
		return -1;
	}

	rc = osmo_pcap_lapd_set_fd(fd);
	if (rc < 0) {
		close(fd);
		return rc;
	}

	return fd;
}

/* This currently only works for the D-Channel */
int osmo_pcap_lapd_write(int fd, int direction, struct msgb *msg)
{
	struct timeval tv;
	struct tmp_pkt {
		struct pcap_rechdr pcap_rechdr;
		struct pcap_lapdhdr header;
		char buf[0];
	} __attribute__((packed));
	const unsigned int total_pkt_len = sizeof(struct tmp_pkt) + msg->len;

	struct tmp_pkt *s = alloca(total_pkt_len);

	/* PCAP file has not been opened, skip. */
	if (fd < 0)
		return 0;

	memset(s, 0, total_pkt_len);

	s->pcap_rechdr.ts_sec	= 0;
	s->pcap_rechdr.ts_usec	= 0;
	s->pcap_rechdr.incl_len   = msg->len + sizeof(struct pcap_lapdhdr);
	s->pcap_rechdr.orig_len   = msg->len + sizeof(struct pcap_lapdhdr);

	if (direction == OSMO_LAPD_PCAP_OUTPUT)
		s->header.pkttype		= htons(LINUX_SLL_OUTGOING);
	else
		s->header.pkttype		= htons(LINUX_SLL_HOST);
	s->header.hatype		= 0;
	s->header.halen		= 0;
	s->header.addr[0]		= 0x01;	/* we are the network side */
	s->header.protocol		= ntohs(48);

	gettimeofday(&tv, NULL);
	s->pcap_rechdr.ts_sec = tv.tv_sec;
	s->pcap_rechdr.ts_usec = tv.tv_usec;

	memcpy(s->buf, msg->data, msg->len);

	if (write(fd, s, total_pkt_len) != total_pkt_len) {
		LOGP(DLLAPD, LOGL_ERROR, "cannot write packet to PCAP: %s\n",
			strerror(errno));
		return -1;
	}
	return total_pkt_len;
}

int osmo_pcap_lapd_close(int fd)
{
	LOGP(DLLAPD, LOGL_NOTICE, "closing LAPD pcap file\n");
	return close(fd);
}