/*
 * ni-client.c
 * This file is part of NetworkInador
 *
 * Copyright (C) 2021 - Gatuno
 *
 * NetworkInador 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.
 *
 * NetworkInador 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 NetworkInador; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, 
 * Boston, MA  02110-1301  USA
 */

#include <glib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

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

#include "ni-client.h"
#include "ni-interface.h"
#include "ni-marshal.h"
#include "ni-route.h"

#include "network-inador-manager.h"

#define COMMAND_SOCKET_PATH "/tmp/network-inador.socket"

/* Privados desde el ni-interface */
void ni_interface_consume_values (NIInterface *ni_interface, char *name, gint arp_type, guint master_index, guint mtu, guint flags);

/* Privados locales */
static void ni_client_dispose (GObject *obj);
static void ni_client_finalize (GObject *obj);

struct _NIClientPrivate {
	int client_socket;
	
	gchar *socket_path;
	GHashTable *interfaces;
	
	GList *routes_v4, *routes_v6;
	GList *route_tables_names;
	guint source;
	guint events;
};

enum {
	PROP_SOCKET_PATH = 1,
	
	N_PROPERTIES
};

static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };

enum {
	DATA_READ,
	
	NEW_INTERFACE,
	DELETE_INTERFACE,
	
	NEW_ROUTE,
	DELETE_ROUTE,
	
	NEW_ROUTE_TABLE,
	DELETE_ROUTE_TABLE,
	
	LAST_SIGNAL
};

static guint signals[LAST_SIGNAL];

G_DEFINE_TYPE_WITH_PRIVATE (NIClient, ni_client, G_TYPE_OBJECT)

static gboolean ni_client_data_read (NIClient *ni_client, gpointer data, guint size);

static void ni_client_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) {
	NIClient *ni_client = NI_CLIENT (object);
	g_return_if_fail (NI_IS_CLIENT (object));

	switch (prop_id) {
		case PROP_SOCKET_PATH:
			if (ni_client->priv->socket_path != NULL) {
				g_free (ni_client->priv->socket_path);
			}
			ni_client->priv->socket_path = g_value_dup_string (value);
			break;
		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
	}
}

static void ni_client_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) {
	NIClient *ni_client = NI_CLIENT (object);
	g_return_if_fail (NI_IS_CLIENT (object));

	switch (prop_id) {
		case PROP_SOCKET_PATH:
			g_value_set_string (value, ni_client->priv->socket_path);
			break;
		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
	}
}

static void ni_client_class_init (NIClientClass *klass) {
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	
	object_class->set_property = ni_client_set_property;
	object_class->get_property = ni_client_get_property;
	object_class->dispose = ni_client_dispose;
	object_class->finalize = ni_client_finalize;
	
	obj_properties[PROP_SOCKET_PATH] = g_param_spec_string (
		"socket-path",
		"Socket Path",
		"The path of the unix socket.",
		COMMAND_SOCKET_PATH,
		G_PARAM_CONSTRUCT | G_PARAM_READWRITE);
	
	g_object_class_install_properties (object_class, N_PROPERTIES, obj_properties);
	
	signals[DATA_READ] = g_signal_new ("packet-read",
		G_TYPE_FROM_CLASS (klass),
		G_SIGNAL_RUN_LAST,
		G_STRUCT_OFFSET (NIClientClass, data_response),
		g_signal_accumulator_true_handled,
		NULL,
		_ni_marshal_BOOLEAN__POINTER_UINT,
		G_TYPE_BOOLEAN,
		2,
		G_TYPE_POINTER | G_SIGNAL_TYPE_STATIC_SCOPE,
		G_TYPE_UINT);
	
	signals[NEW_INTERFACE] = g_signal_new ("new-interface",
		G_TYPE_FROM_CLASS (klass),
		G_SIGNAL_RUN_LAST,
		G_STRUCT_OFFSET (NIClientClass, new_interface),
		NULL,
		NULL,
		g_cclosure_marshal_VOID__OBJECT,
		G_TYPE_NONE,
		1,
		NI_TYPE_INTERFACE);
	
	signals[DELETE_INTERFACE] = g_signal_new ("delete-interface",
		G_TYPE_FROM_CLASS (klass),
		G_SIGNAL_RUN_LAST,
		G_STRUCT_OFFSET (NIClientClass, delete_interface),
		NULL,
		NULL,
		g_cclosure_marshal_VOID__OBJECT,
		G_TYPE_NONE,
		1,
		NI_TYPE_INTERFACE);
	
	signals[NEW_ROUTE] = g_signal_new ("new-route",
		G_TYPE_FROM_CLASS (klass),
		G_SIGNAL_RUN_LAST,
		G_STRUCT_OFFSET (NIClientClass, new_route),
		NULL,
		NULL,
		g_cclosure_marshal_VOID__OBJECT,
		G_TYPE_NONE,
		1,
		NI_TYPE_ROUTE);
	
	signals[DELETE_ROUTE] = g_signal_new ("delete-route",
		G_TYPE_FROM_CLASS (klass),
		G_SIGNAL_RUN_LAST,
		G_STRUCT_OFFSET (NIClientClass, delete_route),
		NULL,
		NULL,
		g_cclosure_marshal_VOID__OBJECT,
		G_TYPE_NONE,
		1,
		NI_TYPE_ROUTE);
	
	signals[NEW_ROUTE_TABLE] = g_signal_new ("new-route-table",
		G_TYPE_FROM_CLASS (klass),
		G_SIGNAL_RUN_LAST,
		G_STRUCT_OFFSET (NIClientClass, new_route_table),
		NULL,
		NULL,
		_ni_marshal_VOID__UINT_STRING,
		G_TYPE_NONE,
		2,
		G_TYPE_UINT,
		G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE);
	
	signals[NEW_ROUTE_TABLE] = g_signal_new ("delete-route-table",
		G_TYPE_FROM_CLASS (klass),
		G_SIGNAL_RUN_LAST,
		G_STRUCT_OFFSET (NIClientClass, delete_route_table),
		NULL,
		NULL,
		g_cclosure_marshal_VOID__UINT,
		G_TYPE_NONE,
		1,
		G_TYPE_UINT);
		
	klass->data_response = ni_client_data_read;
	klass->new_interface = NULL;
	klass->delete_interface = NULL;
	klass->new_route = NULL;
	klass->delete_route = NULL;
	klass->new_route_table = NULL;
	klass->delete_route_table = NULL;
}

static void ni_client_process_interface_response (NIClient *ni_client, gpointer data, guint size) {
	NIInterface *ni_interface;
	unsigned char *buffer = (unsigned char *) data;
	
	uint32_t index, master_index, mtu, type;
	uint16_t flags;
	uint8_t bits;
	guint name_size;
	char iface_name[128];
	
	memcpy (&index, &buffer[2], 4);
	memcpy (&type, &buffer[6], 4);
	memcpy (&master_index, &buffer[10], 4);
	memcpy (&mtu, &buffer[14], 4);
	memcpy (&flags, &buffer[18], 2);
	bits = buffer[20];
	
	name_size = buffer[21];
	memcpy (iface_name, &buffer[22], name_size);
	iface_name[name_size] = 0;
	
	/* Insertar y crear */
	ni_interface = g_object_new (NI_TYPE_INTERFACE, "ni-client", ni_client, "index", index, "wireless", (bits == 0 ? FALSE : TRUE), "type", type, "name", iface_name, "mtu", mtu, "flags", flags, "master-index", master_index, NULL);
	
	g_hash_table_insert (ni_client->priv->interfaces, GINT_TO_POINTER(index), ni_interface);
	
	g_signal_emit (ni_client, signals[NEW_INTERFACE], 0, ni_interface);
}

static NIRoute *_ni_client_search_route (GList *list_routes, sa_family_t family, uint8_t tos, uint32_t table, void *dest, uint32_t prefix, uint32_t priority) {
	GList *g;
	NIRoute *ni_route;
	int family_size = 0;
	
	if (family == AF_INET) {
		family_size = sizeof (struct in_addr);
	} else if (family == AF_INET6) {
		family_size = sizeof (struct in6_addr);
	}
	
	for (g = list_routes; g != NULL; g = g->next) {
		ni_route = (NIRoute *) g->data;
		
		if (ni_route_get_family (ni_route) != family) continue;
		if (ni_route_get_route_tos (ni_route) != tos) continue;
		if (ni_route_get_table (ni_route) != table) continue;
		if (ni_route_get_priority( ni_route) != priority) continue;
		
		if (memcmp (ni_route_get_dest (ni_route), dest, family_size) == 0 && ni_route_get_prefix(ni_route) == prefix) {
			return ni_route;
		}
	}
	
	return NULL;
}

static void ni_client_process_route_response (NIClient *ni_client, gpointer data, guint size) {
	unsigned char *buffer = (unsigned char *) data;
	uint8_t family, family_size;
	uint8_t type, protocol, tos, scope, prefix;
	uint32_t tabla, prioridad;
	uint8_t num_nexthops;
	gboolean has_prefsrc;
	int pos, g;
	NIRouteNH nexthops[16];
	NIRoute *ni_route;
	struct_addr dest, prefsrc;
	
	memset (nexthops, 0, sizeof (nexthops));
	family = buffer[2];
	
	if (family == AF_INET) {
		family_size = sizeof (struct in_addr);
	} else if (family == AF_INET6) {
		family_size = sizeof (struct in6_addr);
	}
	
	type = buffer[3];
	
	memcpy (&tabla, &buffer[4], 4);
	prefix = buffer[8];
	protocol = buffer[9];
	tos = buffer[10];
	scope = buffer[11];
	
	has_prefsrc = FALSE;
	if (buffer[12] & 0x01) {
		has_prefsrc = TRUE;
	}
	
	num_nexthops = buffer[13];
	
	memcpy (&prioridad, &buffer[14], 4);
	
	memcpy (&dest, &buffer[18], family_size);
	
	pos = 18 + family_size;
	if (has_prefsrc) {
		memcpy (&prefsrc, &buffer[pos], family_size);
		pos += family_size;
	}
	
	if (num_nexthops > 16) {
		num_nexthops = 16;
	}
	for (g = 0; g < num_nexthops; g++) {
		nexthops[g].has_gw = FALSE;
		if (buffer[pos] & 0x01) {
			/* Tiene GW */
			nexthops[g].has_gw = TRUE;
		}
		pos++;
		
		nexthops[g].flags = buffer[pos];
		pos++;
		
		nexthops[g].weight = buffer[pos];
		pos++;
		
		pos++; /* El byte ignorado */
		
		memcpy (&nexthops[g].out_index, &buffer[pos], 4);
		pos +=4;
		
		if (nexthops[g].has_gw) {
			memcpy (&nexthops[g].gw, &buffer[pos], family_size);
			pos += family_size;
		}
	}
	ni_route = ni_route_new (ni_client, family, type, tabla, &dest, prefix, protocol, tos, scope, (has_prefsrc ? &prefsrc : NULL), prioridad, num_nexthops, nexthops);
	
	if (family == AF_INET) {
		ni_client->priv->routes_v4 = g_list_append (ni_client->priv->routes_v4, ni_route);
	} else if (family == AF_INET6) {
		ni_client->priv->routes_v6 = g_list_append (ni_client->priv->routes_v6, ni_route);
	}
	g_signal_emit (ni_client, signals[NEW_ROUTE], 0, ni_route);
}

static void ni_client_process_route_del_response (NIClient *ni_client, gpointer data, guint size) {
	unsigned char *buffer = (unsigned char *) data;
	uint8_t family, family_size;
	uint8_t tos, prefix;
	uint32_t tabla, prioridad;
	NIRoute *ni_route;
	struct_addr dest;
	GList *list_routes;
	
	family = buffer[2];
	
	if (family == AF_INET) {
		family_size = sizeof (struct in_addr);
		list_routes = ni_client->priv->routes_v4;
	} else if (family == AF_INET6) {
		family_size = sizeof (struct in6_addr);
		list_routes = ni_client->priv->routes_v6;
	}
	
	tos = buffer[3];
	
	memcpy (&tabla, &buffer[4], 4);
	prefix = buffer[8];
	memcpy (&prioridad, &buffer[9], 4);
	
	memcpy (&dest, &buffer[13], family_size);
	
	ni_route = _ni_client_search_route (list_routes, family, tos, tabla, &dest, prefix, prioridad);
	
	if (ni_route == NULL) {
		/* ¿No hay ruta? Raro */
		return;
	}
	
	/* Desligar la ruta */
	if (family == AF_INET) {
		ni_client->priv->routes_v4 = g_list_remove (ni_client->priv->routes_v4, ni_route);
	} else if (family == AF_INET6) {
		ni_client->priv->routes_v6 = g_list_remove (ni_client->priv->routes_v6, ni_route);
	}
	
	g_signal_emit (ni_client, signals[DELETE_ROUTE], 0, ni_route);
	
	/* Liberar el objeto */
	g_object_unref (ni_route);
}

static void ni_client_process_route_table_name_response (NIClient *ni_client, gpointer data, guint size) {
	unsigned char *buffer = (unsigned char *) data;
	uint8_t name_len;
	uint32_t tabla;
	struct _NIClientRouteTable *rtable;
	
	name_len = buffer[6];
	
	memcpy (&tabla, &buffer[2], 4);
	
	rtable = g_malloc (sizeof (struct _NIClientRouteTable));
	
	rtable->table = tabla;
	memcpy (rtable->name, &buffer[7], name_len);
	rtable->name[name_len] = 0;
	
	ni_client->priv->route_tables_names = g_list_append (ni_client->priv->route_tables_names, rtable);
	
	g_signal_emit (ni_client, signals[NEW_ROUTE_TABLE], 0, rtable->table, rtable->name);
}

static gboolean ni_client_data_read (NIClient *ni_client, gpointer data, guint size) {
	unsigned char *buffer = (unsigned char *) data;
	uint32_t index;
	NIInterface *ni_interface;
	
	if (buffer[0] == NET_INADOR_TYPE_EVENT) {
		switch (buffer[1]) {
			case NET_INADOR_EVENT_IFACE_ADDED:
				memcpy (&index, &buffer[2], 4);
				
				/* Si la interfaz no existe, proccesarla yo, para que se cree el objeto, si la interfaz ya existe, dejar pasar el mensaje */
				ni_interface = (NIInterface *) g_hash_table_lookup (ni_client->priv->interfaces, GINT_TO_POINTER(index));
				
				if (ni_interface == NULL) {
					ni_client_process_interface_response (ni_client, data, size);
					return TRUE;
				} else {
					return FALSE;
				}
				break;
			case NET_INADOR_EVENT_IFACE_REMOVED:
				memcpy (&index, &buffer[2], 4);
				
				/* La interfaz fué eliminada, nosotros procesamos este evento */
				ni_interface = (NIInterface *) g_hash_table_lookup (ni_client->priv->interfaces, GINT_TO_POINTER(index));
				
				if (ni_interface == NULL) {
					/* ¿Evento de una interfaz eliminada? Ignorar */
				} else {
					g_hash_table_remove (ni_client->priv->interfaces, GINT_TO_POINTER(index));
					
					g_signal_emit (ni_client, signals[DELETE_INTERFACE], 0, ni_interface);
					
					g_object_unref (ni_interface);
				}
				return TRUE;
				break;
			case NET_INADOR_EVENT_DHCP_STATUS:
				memcpy (&index, &buffer[2], 4);
				
				/* Si la interfaz no existe, proccesarla yo, para que se cree el objeto, si la interfaz ya existe, dejar pasar el mensaje */
				ni_interface = (NIInterface *) g_hash_table_lookup (ni_client->priv->interfaces, GINT_TO_POINTER(index));
				if (ni_interface == NULL) {
					/* Esta es una condición de error interesante. No debería ocurrir. Absorber el evento */
					return TRUE;
				}
				
				/* Dejar pasar el evento para que la propia interfaz lo procese */
				return FALSE;
				break;
			case NET_INADOR_EVENT_ROUTE_REMOVED:
				/* Eliminar una ruta es ligeramente mas complejo porque necesitamos que haga match con varios valores para eliminarla */
				ni_client_process_route_del_response (ni_client, data, size);
				return TRUE;
				break;
			case NET_INADOR_EVENT_ROUTE_ADDED:
				/* TODO: Revisar si la ruta existe, entonces, en teoría, es una actualización */
				ni_client_process_route_response (ni_client, data, size);
				return FALSE;
				break;
			case NET_INADOR_EVENT_ROUTE_TABLE_ADDED:
				
				return FALSE;
				break;
			case NET_INADOR_EVENT_ROUTE_TABLE_REMOVED:
				
				return FALSE;
				break;
		}
	} else if (buffer[0] == NET_INADOR_TYPE_RESPONSE) {
		switch (buffer[1]) {
			case NET_INADOR_RESPONSE_IFACE:
				memcpy (&index, &buffer[2], 4);
				
				/* Si la interfaz no existe, proccesarla yo, para que se cree el objeto, si la interfaz ya existe, dejar pasar el mensaje */
				ni_interface = (NIInterface *) g_hash_table_lookup (ni_client->priv->interfaces, GINT_TO_POINTER(index));
				
				if (ni_interface == NULL) {
					ni_client_process_interface_response (ni_client, data, size);
					return TRUE;
				} else {
					return FALSE;
				}
				break;
			case NET_INADOR_RESPONSE_DHCP_STATUS:
				memcpy (&index, &buffer[2], 4);
				
				ni_interface = (NIInterface *) g_hash_table_lookup (ni_client->priv->interfaces, GINT_TO_POINTER(index));
				if (ni_interface == NULL) {
					/* Esta es una condición de error interesante. No debería ocurrir. Absorber el evento */
					return TRUE;
				}
				
				/* Dejar pasar el evento para que la propia interfaz lo procese */
				return FALSE;
				break;
			case NET_INADOR_RESPONSE_ROUTE:
				/* Cuando hacemos listado, no hay problemas de actualización */
				ni_client_process_route_response (ni_client, data, size);
				return FALSE;
				break;
			case NET_INADOR_RESPONSE_ROUTE_TABLE:
				ni_client_process_route_table_name_response (ni_client, data, size);
				return FALSE;
				break;
		}
	}
	
	/* Procesar los datos aquí */
	return TRUE;
}

static gboolean ni_client_read_from_socket (GIOChannel *source, GIOCondition condition, gpointer data) {
	NIClient *ni_client = NI_CLIENT (data);
	unsigned char buffer[8192];
	int res;
	gboolean stop;
	int g;
	
	res = recv (ni_client->priv->client_socket, buffer, sizeof (buffer), 0);
	
	if (res <= 0) {
		/* El socket se cerró */
		close (ni_client->priv->client_socket);
		ni_client->priv->client_socket = -1;
		
		/* Eliminar el source */
		if (ni_client->priv->source > 0) {
			g_source_remove (ni_client->priv->source);
			ni_client->priv->source = 0;
		}
		
		/* TODO: Disparar aquí una señal de desconexión al network-inador */
		return FALSE;
	}
	
	printf ("Data arrival: %i\n", res);
	for (g = 0; g < res; g++) {
		printf ("%02hhx ", buffer[g]);
	}
	printf ("\n");
	/* Tenemos datos, hacer una validación básica */
	if (buffer[0] != NET_INADOR_TYPE_RESPONSE &&
	    buffer[0] != NET_INADOR_TYPE_RESPONSE_ERROR &&
	    buffer[0] != NET_INADOR_TYPE_EVENT &&
	    buffer[0] != NET_INADOR_TYPE_RESPONSE_LISTING_END) {
		/* Tipo de paquete inválido */
		return TRUE;
	}
	
	g_signal_emit (ni_client, signals[DATA_READ], 0, (void *) buffer, res, &stop);
	
	return TRUE;
}

static gboolean ni_client_foreach_remove_every_interface (gpointer key, gpointer value, gpointer user_data) {
	NIInterface *ni_interface = NI_INTERFACE (value);
	
	g_object_unref (ni_interface);
	
	return TRUE;
}

static void ni_client_dispose (GObject *obj) {
	NIClient *ni_client;
	GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT);
	
	ni_client = NI_CLIENT (obj);
	
	/* Recorrer todos los objetos interface para quitarles una referencia y liberarlos */
	g_hash_table_foreach_remove (ni_client->priv->interfaces, ni_client_foreach_remove_every_interface, NULL);
	
	if (ni_client->priv->source > 0) {
		g_source_remove (ni_client->priv->source);
		ni_client->priv->source = 0;
	}
	
	parent_class->dispose (obj);
}

static void ni_client_finalize (GObject *obj) {
	NIClient *ni_client;
	GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT);
	
	ni_client = NI_CLIENT (obj);
	
	g_hash_table_destroy (ni_client->priv->interfaces);
	
	if (ni_client->priv->client_socket >= 0) {
		close (ni_client->priv->client_socket);
		ni_client->priv->client_socket = 0;
	}
	
	if (ni_client->priv->socket_path != NULL) {
		g_free (ni_client->priv->socket_path);
		ni_client->priv->socket_path = NULL;
	}
	
	parent_class->finalize (obj);
}

static void ni_client_init (NIClient *ni_client) {
	NIClientPrivate *priv = ni_client_get_instance_private (ni_client);
	ni_client->priv = priv;
	
	priv->client_socket = -1;
	priv->socket_path = NULL;
	priv->routes_v4 = NULL;
	priv->routes_v6 = NULL;
	priv->route_tables_names = NULL;
	priv->source = 0;
	priv->events = NET_INADOR_EVENT_MASK_INTERFACES | NET_INADOR_EVENT_MASK_IP | NET_INADOR_EVENT_MASK_DHCP_STATUS | NET_INADOR_EVENT_MASK_ROUTES;
	
	priv->interfaces = g_hash_table_new (g_direct_hash, g_direct_equal);
}

void ni_client_update_event_mask (NIClient *ni_client) {
	unsigned char buffer[8];
	
	if (ni_client->priv->client_socket < 0) {
		return;
	}
	
	buffer[0] = NET_INADOR_TYPE_COMMAND;
	buffer[1] = NET_INADOR_COMMAND_SET_EVENT_MASK;
	memcpy (&buffer[2], &ni_client->priv->events, 4);
	
	send (ni_client->priv->client_socket, buffer, 6, 0);
}

void ni_client_ask_interfaces (NIClient *ni_client) {
	unsigned char buffer[8];
	
	if (ni_client->priv->client_socket < 0) {
		return;
	}
	
	buffer[0] = NET_INADOR_TYPE_COMMAND;
	buffer[1] = NET_INADOR_COMMAND_LIST_IFACES;
	
	send (ni_client->priv->client_socket, buffer, 2, 0);
}

void ni_client_ask_routes (NIClient *ni_client) {
	unsigned char buffer[8];
	
	if (ni_client->priv->client_socket < 0) {
		return;
	}
	
	buffer[0] = NET_INADOR_TYPE_COMMAND;
	buffer[1] = NET_INADOR_COMMAND_LIST_ROUTES;
	memset (&buffer[2], 0, 4);
	buffer[6] = AF_UNSPEC;
	
	send (ni_client->priv->client_socket, buffer, 7, 0);
}

void ni_client_ask_route_tables_names (NIClient *ni_client) {
	unsigned char buffer[8];
	
	if (ni_client->priv->client_socket < 0) {
		return;
	}
	
	buffer[0] = NET_INADOR_TYPE_COMMAND;
	buffer[1] = NET_INADOR_COMMAND_LIST_ROUTE_TABLES;
	memset (&buffer[2], 0, 4);
	
	send (ni_client->priv->client_socket, buffer, 6, 0);
}

void ni_client_ask_ip_new (NIClient *ni_client, NIInterface *ni_interface, int family, int prefix, struct_addr *addr, uint32_t flags, unsigned char scope, int has_local, struct_addr *local_addr, int has_brd, struct_addr *brd_addr, uint32_t *cacheinfo) {
	unsigned char buffer[80];
	uint32_t index;
	int family_size;
	int pos;
	
	if (ni_client->priv->client_socket < 0) {
		return;
	}
	
	if (family == AF_INET) {
		family_size = sizeof (struct in_addr);
	} else if (family == AF_INET6) {
		family_size = sizeof (struct in6_addr);
	}
	
	index = ni_interface_get_index (ni_interface);
	buffer[0] = NET_INADOR_TYPE_COMMAND;
	buffer[1] = NET_INADOR_COMMAND_ADD_IP;
	memcpy (&buffer[2], &index, 4);
	buffer[6] = family;
	buffer[7] = prefix;
	
	buffer[8] = 0;
	if (has_local) {
		buffer[8] |= 0x01;
	}
	
	if (has_brd) {
		buffer[8] |= 0x02;
	}
	
	buffer[9] = scope;
	memcpy (&buffer[10], &flags, 4);
	
	/* Copiar los timestamp solicitados */
	memcpy (&buffer[14], cacheinfo, 8);
	
	memcpy (&buffer[22], addr, family_size);
	
	pos = 22 + family_size;
	
	if (has_local) {
		memcpy (&buffer[pos], local_addr, family_size);
		pos += family_size;
	}
	
	if (has_brd) {
		memcpy (&buffer[pos], brd_addr, family_size);
		pos += family_size;
	}
	
	send (ni_client->priv->client_socket, buffer, pos, 0);
}

void ni_client_ask_ip_delete (NIClient *ni_client, NIInterface *ni_interface, NIIP *ni_ip) {
	unsigned char buffer[32];
	uint32_t index;
	int family_size;
	int pos;
	const struct_addr *addr, *local_addr;
	
	int family = ni_ip_get_family (ni_ip);
	
	if (ni_client->priv->client_socket < 0) {
		return;
	}
	
	if (family == AF_INET) {
		family_size = sizeof (struct in_addr);
	} else if (family == AF_INET6) {
		family_size = sizeof (struct in6_addr);
	}
	
	index = ni_interface_get_index (ni_interface);
	buffer[0] = NET_INADOR_TYPE_COMMAND;
	buffer[1] = NET_INADOR_COMMAND_REMOVE_IP;
	memcpy (&buffer[2], &index, 4);
	buffer[6] = family;
	buffer[7] = ni_ip_get_prefix (ni_ip);
	
	buffer[8] = 0;
	if (ni_ip_has_local (ni_ip)) {
		buffer[8] |= 0x01;
	}
	
	addr = ni_ip_get_addr (ni_ip);
	memcpy (&buffer[9], addr, family_size);
	pos = 9 + family_size;
	
	if (ni_ip_has_local (ni_ip)) {
		local_addr = ni_ip_get_local_addr (ni_ip);
		memcpy (&buffer[9 + family_size], local_addr, family_size);
		pos += family_size;
	}
	
	send (ni_client->priv->client_socket, buffer, pos, 0);
}

void ni_client_ask_change_iface_name (NIClient *ni_client, NIInterface *ni_interface, const gchar *new_name) {
	unsigned char buffer[128];
	uint32_t index;
	int n_len;
	
	if (ni_client->priv->client_socket < 0) {
		return;
	}
	
	if (new_name[0] == 0) return;
	
	index = ni_interface_get_index (ni_interface);
	buffer[0] = NET_INADOR_TYPE_COMMAND;
	buffer[1] = NET_INADOR_COMMAND_IFACE_CHANGE_NAME;
	memcpy (&buffer[2], &index, 4);
	n_len = strlen (new_name);
	buffer[6] = n_len;
	memcpy (&buffer[7], new_name, buffer[6]);
	
	send (ni_client->priv->client_socket, buffer, 7 + n_len, 0);
}

void ni_client_ask_change_iface_mtu (NIClient *ni_client, NIInterface *ni_interface, guint new_mtu) {
	unsigned char buffer[128];
	uint32_t index, mtu;
	
	if (ni_client->priv->client_socket < 0) {
		return;
	}
	
	index = ni_interface_get_index (ni_interface);
	buffer[0] = NET_INADOR_TYPE_COMMAND;
	buffer[1] = NET_INADOR_COMMAND_IFACE_CHANGE_MTU;
	memcpy (&buffer[2], &index, 4);
	//mtu = htonl (new_mtu);
	mtu = new_mtu;
	memcpy (&buffer[6], &mtu, 4);
	
	send (ni_client->priv->client_socket, buffer, 10, 0);
}

void ni_client_ask_ip_interface (NIClient *ni_client, NIInterface *ni_interface, int family) {
	unsigned char buffer[8];
	uint32_t index;
	
	if (ni_client->priv->client_socket < 0) {
		return;
	}
	
	index = ni_interface_get_index (ni_interface);
	buffer[0] = NET_INADOR_TYPE_COMMAND;
	buffer[1] = NET_INADOR_COMMAND_LIST_IP;
	memcpy (&buffer[2], &index, 4);
	buffer[6] = family;
	
	send (ni_client->priv->client_socket, buffer, 7, 0);
}

void ni_client_ask_up_down_interface (NIClient *ni_client, NIInterface *ni_interface, gboolean is_up) {
	unsigned char buffer[8];
	uint32_t index;
	
	if (ni_client->priv->client_socket < 0) {
		return;
	}
	
	index = ni_interface_get_index (ni_interface);
	buffer[0] = NET_INADOR_TYPE_COMMAND;
	buffer[1] = (is_up ? NET_INADOR_COMMAND_IFACE_UP : NET_INADOR_COMMAND_IFACE_DOWN);
	memcpy (&buffer[2], &index, 4);
	
	send (ni_client->priv->client_socket, buffer, 6, 0);
}

void ni_client_ask_dhcp_run_interface (NIClient *ni_client, NIInterface *ni_interface, int type, guint flags) {
	unsigned char buffer[16];
	uint32_t index;
	uint32_t flags_to_send = flags;
	
	if (ni_client->priv->client_socket < 0) {
		return;
	}
	
	if (type < 1 || type > 2) {
		/* Solo se permite tipo 1 o tipo 2 */
		return;
	}
	
	index = ni_interface_get_index (ni_interface);
	buffer[0] = NET_INADOR_TYPE_COMMAND;
	buffer[1] = NET_INADOR_COMMAND_RUN_DHCP;
	memcpy (&buffer[2], &index, 4);
	buffer[6] = AF_INET;
	buffer[7] = type;
	
	memcpy (&buffer[8], &flags_to_send, 4);
	
	send (ni_client->priv->client_socket, buffer, 12, 0);
}

void ni_client_ask_dhcp_stop_interface (NIClient *ni_client, NIInterface *ni_interface) {
	unsigned char buffer[8];
	uint32_t index;
	
	if (ni_client->priv->client_socket < 0) {
		return;
	}
	
	index = ni_interface_get_index (ni_interface);
	buffer[0] = NET_INADOR_TYPE_COMMAND;
	buffer[1] = NET_INADOR_COMMAND_STOP_DHCP;
	memcpy (&buffer[2], &index, 4);
	buffer[6] = AF_INET;
	
	send (ni_client->priv->client_socket, buffer, 7, 0);
}

void ni_client_ask_create_bridge (NIClient *ni_client, const gchar *name) {
	unsigned char buffer[128];
	int n_len;
	
	if (ni_client->priv->client_socket < 0) {
		return;
	}
	
	if (name[0] == 0) return;
	
	buffer[0] = NET_INADOR_TYPE_COMMAND;
	buffer[1] = NET_INADOR_COMMAND_CREATE_BRIDGE;
	n_len = strlen (name);
	buffer[2] = n_len;
	memcpy (&buffer[3], name, buffer[2]);
	
	send (ni_client->priv->client_socket, buffer, 3 + n_len, 0);
}

void ni_client_ask_interface_clear_master (NIClient *ni_client, NIInterface *ni_interface) {
	unsigned char buffer[128];
	uint32_t index;
	
	if (ni_client->priv->client_socket < 0) {
		return;
	}
	
	buffer[0] = NET_INADOR_TYPE_COMMAND;
	buffer[1] = NET_INADOR_COMMAND_CLEAR_MASTER;
	index = ni_interface_get_index (ni_interface);
	memcpy (&buffer[2], &index, 4);
	
	send (ni_client->priv->client_socket, buffer, 6, 0);
}

void ni_client_ask_interface_set_master (NIClient *ni_client, NIInterface *ni_interface, NIInterface *master) {
	unsigned char buffer[128];
	uint32_t index;
	
	if (ni_client->priv->client_socket < 0) {
		return;
	}
	
	buffer[0] = NET_INADOR_TYPE_COMMAND;
	buffer[1] = NET_INADOR_COMMAND_SET_MASTER;
	index = ni_interface_get_index (ni_interface);
	memcpy (&buffer[2], &index, 4);
	index = ni_interface_get_index (master);
	memcpy (&buffer[6], &index, 4);
	
	send (ni_client->priv->client_socket, buffer, 10, 0);
}

void ni_client_ask_interface_dhcp_status (NIClient *ni_client, NIInterface *ni_interface) {
	unsigned char buffer[8];
	uint32_t index;
	
	if (ni_client->priv->client_socket < 0) {
		return;
	}
	
	index = ni_interface_get_index (ni_interface);
	buffer[0] = NET_INADOR_TYPE_COMMAND;
	buffer[1] = NET_INADOR_COMMAND_GET_DHCP_STATUS;
	memcpy (&buffer[2], &index, 4);
	buffer[6] = AF_INET;
	
	send (ni_client->priv->client_socket, buffer, 7, 0);
}

void ni_client_ask_route_new (NIClient *ni_client, int family, int prefix, struct_addr *dest, int type, uint32_t tabla, int protocol, int tos, int scope, int has_prefsrc, struct_addr *prefsrc, uint32_t priority, GList *gws) {
	unsigned char buffer[80];
	int family_size;
	int pos, c;
	GList *g;
	NIRouteNH *nexthop;
	
	if (ni_client->priv->client_socket < 0) {
		return;
	}
	
	if (family == AF_INET) {
		family_size = sizeof (struct in_addr);
	} else if (family == AF_INET6) {
		family_size = sizeof (struct in6_addr);
	}
	
	buffer[0] = NET_INADOR_TYPE_COMMAND;
	buffer[1] = NET_INADOR_COMMAND_ADD_ROUTE;
	buffer[2] = family;
	buffer[3] = type;
	memcpy (&buffer[4], &tabla, 4);
	
	buffer[8] = prefix;
	buffer[9] = protocol;
	buffer[10] = tos;
	buffer[11] = scope;
	
	buffer[12] = 0;
	
	if (has_prefsrc) {
		buffer[12] |= 0x01;
	}
	
	/* La cantidad de gateways que voy a enviar con esta ruta */
	g = gws;
	c = 0;
	while (g != NULL) {
		c++;
		g = g->next;
	}
	buffer[13] = c;
	
	memcpy (&buffer[14], &priority, 4);
	
	memcpy (&buffer[18], dest, family_size);
	
	pos = 18 + family_size;
	
	if (has_prefsrc) {
		memcpy (&buffer[pos], prefsrc, family_size);
		pos += family_size;
	}
	
	g = gws;
	while (g != NULL) {
		nexthop = (NIRouteNH *) g->data;
		
		buffer[pos] = 0;
		if (nexthop->has_gw) {
			buffer[pos] |= 0x01;
		}
		
		buffer[pos + 1] = nexthop->flags;
		buffer[pos + 2] = nexthop->weight;
		buffer[pos + 3] = 0;
		memcpy (&buffer[pos + 4], &nexthop->out_index, 4);
		
		if (nexthop->has_gw) {
			memcpy (&buffer[pos + 8], &nexthop->gw, family_size);
			pos += (8 + family_size);
		} else {
			pos += 8;
		}
		
		g = g->next;
	}
	
	send (ni_client->priv->client_socket, buffer, pos, 0);
}

void ni_client_ask_route_del (NIClient *ni_client, NIRoute *route) {
	unsigned char buffer[80];
	int family_size, family;
	int pos, c;
	const struct_addr *dest;
	uint32_t tabla, priority;
	
	if (ni_client->priv->client_socket < 0) {
		return;
	}
	
	family = ni_route_get_family (route);
	
	if (family == AF_INET) {
		family_size = sizeof (struct in_addr);
	} else if (family == AF_INET6) {
		family_size = sizeof (struct in6_addr);
	}
	
	buffer[0] = NET_INADOR_TYPE_COMMAND;
	buffer[1] = NET_INADOR_COMMAND_REMOVE_ROUTE;
	buffer[2] = family;
	buffer[3] = ni_route_get_route_tos (route);
	
	tabla = ni_route_get_table (route);
	memcpy (&buffer[4], &tabla, 4);
	
	buffer[8] = ni_route_get_prefix (route);
	priority = ni_route_get_priority (route);
	memcpy (&buffer[9], &priority, 4);
	
	dest = ni_route_get_dest (route);
	memcpy (&buffer[13], dest, family_size);
	
	pos = 13 + family_size;
	
	send (ni_client->priv->client_socket, buffer, pos, 0);
}

static void ni_client_foreach_has_table (gpointer key, gpointer value, gpointer data) {
	GList **lista = (GList **) data;
	
	*lista = g_list_append (*lista, value);
}

GList *ni_client_get_list_interfaces (NIClient *ni_client) {
	g_return_val_if_fail (NI_IS_CLIENT (ni_client), NULL);
	GList *all_ifaces = NULL;
	
	g_hash_table_foreach (ni_client->priv->interfaces, ni_client_foreach_has_table, &all_ifaces);
	
	return all_ifaces;
}

NIInterface *ni_client_get_interface_by_index (NIClient *ni_client, guint index) {
	g_return_val_if_fail (NI_IS_CLIENT (ni_client), NULL);
	return g_hash_table_lookup (ni_client->priv->interfaces, GINT_TO_POINTER(index));
}

GList *ni_client_get_routes_v4 (NIClient *ni_client) {
	g_return_val_if_fail (NI_IS_CLIENT (ni_client), NULL);
	
	return ni_client->priv->routes_v4;
}

GList *ni_client_get_routes_v6 (NIClient *ni_client) {
	g_return_val_if_fail (NI_IS_CLIENT (ni_client), NULL);
	
	return ni_client->priv->routes_v6;
}

GList *ni_client_get_route_tables_names (NIClient *ni_client) {
	g_return_val_if_fail (NI_IS_CLIENT (ni_client), NULL);
	
	return ni_client->priv->route_tables_names;
}

gboolean ni_client_connect (NIClient *ni_client) {
	int s, ret;
	struct sockaddr_un path_dest;
	GIOChannel *channel;
	
	if (ni_client->priv->client_socket >= 0) {
		return FALSE;
	}
	
	s = socket (AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0);
	
	if (s < 0) {
		return FALSE;
	}
	
	path_dest.sun_family = AF_UNIX;
	strncpy (path_dest.sun_path, ni_client->priv->socket_path, sizeof (path_dest.sun_path));
	ret = connect (s, (struct sockaddr *) &path_dest, sizeof (path_dest));
	
	if (ret < 0) {
		perror ("Connect");
		close (s);
		return FALSE;
	}
	
	/* Tengo socket, instalar vigilante */
	ni_client->priv->client_socket = s;
	channel = g_io_channel_unix_new (s);
	ni_client->priv->source = g_io_add_watch (channel, G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP, ni_client_read_from_socket, ni_client);
	g_io_channel_unref (channel);
	
	/* Pedir eventos */
	ni_client_update_event_mask (ni_client);
	
	/* Pedir un listado de interfaces */
	ni_client_ask_interfaces (ni_client);
	
	/* Pedir un listado de rutas */
	ni_client_ask_routes (ni_client);
	
	/* Pedir un listado de los nombres de las tablas */
	ni_client_ask_route_tables_names (ni_client);
}

void ni_client_disconnect (NIClient *ni_client) {
	/* TODO: Escribir esto */
}

NIClient *ni_client_new (void) {
	return g_object_new (NI_TYPE_CLIENT, NULL);
}

NIClient *ni_client_new_with_path (const char *path) {
	return g_object_new (NI_TYPE_CLIENT, "socket-path", path, NULL);
}