/*
 * Copyright (c) 2009-2011, Broadcom Corporation
 * Copyright (c) 2014, QLogic Corporation
 *
 * Written by:  Benjamin Li  (benli@broadcom.com)
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Adam Dunkels.
 * 4. The name of the author may not be used to endorse or promote
 *    products derived from this software without specific prior
 *    written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * nic_vlan.c - uIP user space stack VLAN utilities
 *
 */
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/stat.h>

#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

#include "logger.h"
#include "nic.h"
#include "nic_utils.h"
#include "nic_vlan.h"

/*******************************************************************************
 * Constants
 ******************************************************************************/
#define PFX "vlan"

static const char proc_vlan_config_path[] = "/proc/net/vlan/config";

/*******************************************************************************
 * Resolving Found VLAN's for CNIC
 ******************************************************************************/
int init_vlan_found_handle(struct vlan_found_handle *found_handle,
			   struct vlan_handle *handle)
{
	memset(found_handle, 0, sizeof(*found_handle));

	found_handle->entries = malloc(found_handle->num_of_entries *
				       sizeof(struct vlan_found_entry));
	if (found_handle->entries == NULL) {
		ILOG_ERR("Could not allocate space for found entries");
		return -ENOMEM;
	}

	found_handle->handle = handle;
	found_handle->num_of_entries = handle->num_of_entries;

	memset(found_handle->entries, 0, found_handle->num_of_entries *
	       sizeof(struct vlan_found_entry));

	handle->outstanding_found_handles++;

	return 0;
}

void release_vlan_found_handle(struct vlan_found_handle *found_handle)
{
	if (found_handle->entries != NULL) {
		free(found_handle->entries);
		found_handle->entries = NULL;
	}

	found_handle->num_of_entries = 0;

	found_handle->handle->outstanding_found_handles--;

	found_handle->handle = NULL;

}

/*******************************************************************************
 * Resolving VLAN's for CNIC
 ******************************************************************************/
/**
 *  init_vlan_handle() - Used to initialize struct ipv4_route_handle so
 *                            that is can be used
 *  @param handle - Pointer to struct ipv4_route_handle to initialize
 *  @return 0 on success and <0 on failure
 */
void init_vlan_table(struct vlan_handle *handle)
{
	handle->entries = NULL;
	handle->num_of_entries = 0;
}

/**
 *  parse_vlan_table() - Given the raw dump of a Linux vlan table, this
 *                       function will parse the into entries held by
 *                       struct vlan_handle
 *  @param handle - struct vlan_handle used to hold the parsed contents
 *  @param raw    - buffer to parse the contents from
 *  @param raw_size  - size of the buffer in bytes
 *  @return 0 on success, <0 on failure
 */
int parse_vlan_table(struct vlan_handle *handle, char *raw, uint32_t raw_size)
{
	FILE *fp;
	int i;
	char *token;
	size_t size;
	int rc;

	token = raw;

	/*  determine the number of entries */
	while (*token != '\0') {
		if (*token == '\n')
			handle->num_of_entries++;

		token++;
	}

	/*  There are 2 lines which describe the vlan table
	 *  This lines need to be skipped with counting */
	handle->num_of_entries -= 2;

	ILOG_INFO("Number of vlan entries: %d", handle->num_of_entries);

	size = handle->num_of_entries * sizeof(struct vlan_entry);
	handle->entries = malloc(size);
	if (handle->entries == NULL) {
		ILOG_ERR("Couldn't malloc space to parse vlan table. entires: %d size: %d",
		     handle->num_of_entries, size);
		return -ENOMEM;
	}

	fp = fmemopen(raw, raw_size, "r");
	if (fp == NULL) {
		ILOG_ERR("Could not open raw dump of vlan table");
		rc = errno;
		goto fmemopen_error;
	}

	if (fscanf(fp, "%*[^\n]\n") < 0) {	/* Skip the first line. */
		ILOG_ERR("Empty or missing line, or read error");
		rc = -EIO;
		goto error;
	}

	if (fscanf(fp, "%*[^\n]\n") < 0) {	/* Skip the second line. */
		ILOG_ERR("Empty or missing line, or read error");
		rc = -EIO;
		goto error;
	}

	i = 0;
	/*  Time to parse the routing table */
	while (1) {
		struct vlan_entry *entry = &handle->entries[i];
		int r;

		r = fscanf(fp, "%15s |%hu |%15s",
			   entry->vlan_iface_name,
			   &entry->vlan_id, entry->phy_iface_name);
		if (r != 3) {
			if (feof(fp)) {	/* EOF with no (nonspace) chars read. */
				break;
			}

			ILOG_WARN("Parsing error: parsed %d elements", r);
			break;
		}

		i++;

		ILOG_DEBUG("Vlan %d: vlan iface:%s vlan id:%d phys iface:%s",
			  i,
			  entry->vlan_iface_name,
			  entry->vlan_id, entry->phy_iface_name);
	}

	fclose(fp);

	return 0;

error:
	fclose(fp);

fmemopen_error:
	if (handle->entries != NULL)
		free(handle->entries);

	return rc;
}

/**
 *  capture_vlan_table() - This function will snapshot the Linux vlan
 *                         routing table for further processing
 *  @param handle - struct vlan_handle used to hold the routing context
 *  @return 0 on success, <0 on failure
 */
int capture_vlan_table(struct vlan_handle *handle)
{
	char *raw = NULL;
	uint32_t raw_size = 0;
	int rc;

	rc = capture_file(&raw, &raw_size, proc_vlan_config_path);
	if (rc != 0)
		goto error;

	rc = parse_vlan_table(handle, raw, raw_size);
	if (rc != 0)
		goto error;

error:
	if (raw != NULL)
		free(raw);

	return rc;
}

/**
 *  release_vlan_table() - This function will free all resources used by
 *                         the handle
 *  @param handle -  struct vlan_handle used to hold the routing context
 */
void release_vlan_table(struct vlan_handle *handle)
{
	if (handle->entries != NULL) {
		free(handle->entries);
		handle->entries = NULL;
	}

	handle->num_of_entries = 0;
}

/**
 *  find_phy_using_vlan_interface() - Given the interface name determine VLAN
 *      tag ID to match either the physical or VLAN interface name
 *  @param vlan_iface_name - VLAN interface used to find the physical
 *                           interface
 *  @param phy_iface_name - returned value is the physical interface name
 *  @param vlan_id - returned value is the VLAN id
 *  @return 1 is returned if the interface is a VLAN, 0 if the interface is not
 *          <0 is returned if there is an error
 */
int find_phy_using_vlan_interface(struct vlan_handle *handle,
				  char *vlan_iface_name,
				  char **phy_iface_name, uint16_t *vlan_id)
{
	int i, rc = 0;

	for (i = 0; i < handle->num_of_entries; i++) {
		struct vlan_entry *entry = &handle->entries[i];

		/*  Compare VLAN interface names to find a match */
		if (strcmp(entry->vlan_iface_name, vlan_iface_name) == 0) {
			*phy_iface_name = entry->phy_iface_name;
			*vlan_id = entry->vlan_id;
			rc = 1;
			break;
		}
	}

	return rc;
}

/**
 *  find_vlans_using_phy_interface() - Given the physical interface name this
 *      function will determine the VLAN interface name and VLAN ID
 *  @param iface_name - physical interface used to find the vlan interface
 *  @param vlan_iface_name - returned value is the VLAN interface name
 *  @return The number of VLAN interfaces found
 */
int find_vlans_using_phy_interface(struct vlan_handle *handle,
				   struct vlan_found_handle *found_handle,
				   char *phy_iface_name)
{
	int i, num_found = 0;

	for (i = 0; i < handle->num_of_entries; i++) {
		struct vlan_entry *entry = &handle->entries[i];

		/*  Compare interface names to find a match */
		if (strcmp(entry->phy_iface_name, phy_iface_name) == 0) {
			found_handle->entries[i].found = VLAN_ENTRY_FOUND;
			num_found++;
		}
	}

	return num_found;
}

/**
 *  valid_vlan() - determine if the vlan value which is passed is valid
 *  @param vlan - vlan value to test
 *  @return 0 - not valid, 1 - valid
 */
int valid_vlan(short int vlan)
{
	/* Allow vlan 1 to connect */
	if (vlan > 0 && vlan < 4095)
		return 1;

	return 0;
}
