/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* NOTICE: This is a clean room re-implementation of libnl */

#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include "netlink-types.h"

#define NL_BUFFER_SZ (32768U)

/* Checks message for completeness and sends it out */
int nl_send_auto_complete(struct nl_sock *sk, struct nl_msg *msg)
{
	struct nlmsghdr *nlh = msg->nm_nlh;
	struct timeval tv;

	if (!nlh) {
		int errsv = errno;
		fprintf(stderr, "Netlink message header is NULL!\n");
		return -errsv;
	}

	/* Complete the nl_msg header */
	if (gettimeofday(&tv, NULL))
		nlh->nlmsg_seq = 1;
	else
		nlh->nlmsg_seq = (int) tv.tv_sec;
	nlh->nlmsg_pid = sk->s_local.nl_pid;
	nlh->nlmsg_flags |= NLM_F_REQUEST | NLM_F_ACK;

	return nl_send(sk, msg);
}

/* Receives a netlink message, allocates a buffer in *buf and stores
 * the message content. The peer's netlink address is stored in
 * *nla. The caller is responsible for freeing the buffer allocated in
 * *buf if a positive value is returned. Interrupted system calls are
 * handled by repeating the read. The input buffer size is determined
 * by peeking before the actual read is done */
int nl_recv(struct nl_sock *sk, struct sockaddr_nl *nla, \
	unsigned char **buf, struct ucred **creds)
{
	int rc = -1;
	int sk_flags;
	int RECV_BUF_SIZE = getpagesize();
	int errsv;
	struct iovec recvmsg_iov;
	struct msghdr msg;

	/* Allocate buffer */
	*buf = (unsigned char *) malloc(RECV_BUF_SIZE);
	if (!(*buf)) {
		rc = -ENOMEM;
		goto fail;
	}

	/* Prepare to receive message */
	recvmsg_iov.iov_base = *buf;
	recvmsg_iov.iov_len = RECV_BUF_SIZE;

	msg.msg_name = (void *) &sk->s_peer;
	msg.msg_namelen = sizeof(sk->s_peer);
	msg.msg_iov = &recvmsg_iov;
	msg.msg_iovlen = 1;
	msg.msg_control = NULL;
	msg.msg_controllen = 0;
	msg.msg_flags = 0;

	/* Make non blocking and then restore previous setting */
	sk_flags = fcntl(sk->s_fd, F_GETFL, 0);
	fcntl(sk->s_fd, F_SETFL, O_NONBLOCK);
	rc = recvmsg(sk->s_fd, &msg, 0);
	errsv = errno;
	fcntl(sk->s_fd, F_SETFL, sk_flags);

	if (rc < 0) {
		rc = -errsv;
		free(*buf);
		*buf = NULL;
	}

fail:
	return rc;
}

/* Receive a set of messages from a netlink socket */
/* NOTE: Does not currently support callback replacements!!! */
int nl_recvmsgs(struct nl_sock *sk, struct nl_cb *cb)
{
	struct sockaddr_nl nla;
	struct ucred *creds;

	int rc, cb_rc = NL_OK, done = 0;

	do {
		unsigned char *buf;
		int i, rem, flags;
		struct nlmsghdr *nlh;
		struct nlmsgerr *nlme;
		struct nl_msg *msg;

		done = 0;
		rc = nl_recv(sk, &nla, &buf, &creds);
		if (rc < 0)
			break;

		nlmsg_for_each_msg(nlh, (struct nlmsghdr *) buf, rc, rem) {

			if (rc <= 0 || cb_rc == NL_STOP)
				break;

			/* Check for callbacks */

			msg = (struct nl_msg *) malloc(sizeof(struct nl_msg));
			memset(msg, 0, sizeof(*msg));
			msg->nm_nlh = nlh;

			/* Check netlink message type */

			switch (msg->nm_nlh->nlmsg_type) {
			case NLMSG_ERROR:	  /* Used for ACK too */
				/* Certainly we should be doing some
				 * checking here to make sure this
				 * message is intended for us */
				nlme = nlmsg_data(msg->nm_nlh);
				if (nlme->error == 0)
					msg->nm_nlh->nlmsg_flags |= NLM_F_ACK;

				rc = nlme->error;
				cb_rc = cb->cb_err(&nla, nlme, cb->cb_err_arg);
				nlme = NULL;
				break;

			case NLMSG_DONE:
				done = 1;

			case NLMSG_OVERRUN:
			case NLMSG_NOOP:
			default:
				break;
			};

			for (i = 0; i <= NL_CB_TYPE_MAX; i++) {

				if (cb->cb_set[i]) {
					switch (i) {
					case NL_CB_VALID:
						if (rc > 0)
							cb_rc = cb->cb_set[i](msg, cb->cb_args[i]);
						break;

					case NL_CB_FINISH:
						if ((msg->nm_nlh->nlmsg_flags & NLM_F_MULTI) &&
							(msg->nm_nlh->nlmsg_type & NLMSG_DONE))
							cb_rc = cb->cb_set[i](msg, cb->cb_args[i]);

						break;

					case NL_CB_ACK:
						if (msg->nm_nlh->nlmsg_flags & NLM_F_ACK)
							cb_rc = cb->cb_set[i](msg, cb->cb_args[i]);

						break;
					default:
						break;
					}
				}
			}

			free(msg);
			if (done)
				break;
		}
		free(buf);
		buf = NULL;

		if (done)
			break;
	} while (rc > 0 && cb_rc != NL_STOP);

success:
fail:
	return rc;
}

/* Send raw data over netlink socket */
int nl_send(struct nl_sock *sk, struct nl_msg *msg)
{
	struct nlmsghdr *nlh = nlmsg_hdr(msg);
	struct iovec msg_iov;

	/* Create IO vector with Netlink message */
	msg_iov.iov_base = nlh;
	msg_iov.iov_len = nlh->nlmsg_len;

	return nl_send_iovec(sk, msg, &msg_iov, 1);
}

/* Send netlink message */
int nl_send_iovec(struct nl_sock *sk, struct nl_msg *msg,
		   struct iovec *iov, unsigned iovlen)
{
	int rc;

	/* Socket message */
	struct msghdr mh = {
		.msg_name = (void *) &sk->s_peer,
		.msg_namelen = sizeof(sk->s_peer),
		.msg_iov = iov,
		.msg_iovlen = iovlen,
		.msg_control = NULL,
		.msg_controllen = 0,
		.msg_flags = 0
	};

	/* Send message and verify sent */
	rc = nl_sendmsg(sk, (struct nl_msg *) &mh, 0);
	if (rc < 0)
		fprintf(stderr, "Error sending netlink message: %d\n", errno);
	return rc;

}

/* Send netlink message with control over sendmsg() message header */
int nl_sendmsg(struct nl_sock *sk, struct nl_msg *msg, struct msghdr *hdr)
{
	return sendmsg(sk->s_fd, (struct msghdr *) msg, (int) hdr);
}

/* Create and connect netlink socket */
int nl_connect(struct nl_sock *sk, int protocol)
{
	struct sockaddr addr;
	socklen_t addrlen;
	int rc;

	/* Create RX socket */
	sk->s_fd = socket(PF_NETLINK, SOCK_RAW, protocol);
	if (sk->s_fd < 0)
		return -errno;

	/* Set size of RX and TX buffers */
	if (nl_socket_set_buffer_size(sk, NL_BUFFER_SZ, NL_BUFFER_SZ) < 0)
		return -errno;

	/* Bind RX socket */
	rc = bind(sk->s_fd, (struct sockaddr *)&sk->s_local, \
		sizeof(sk->s_local));
	if (rc < 0)
		return -errno;
	addrlen = sizeof(addr);
	getsockname(sk->s_fd, &addr, &addrlen);

	return 0;

}
