/*
 Copyright (c) 2013 Mats Mattsson

 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
 rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 permit persons to whom the Software is furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <arpa/inet.h>
#include <errno.h>
#include <inttypes.h>
#include <fcntl.h>
#include <getopt.h>
#include <grp.h>
#include <netdb.h>
#include <pwd.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <uuid/uuid.h>

#define P_COLOR( file, fileno, color, ... ) ({ \
	int const can_color = isatty(fileno) && (NULL != strstr(getenv("TERM"), "color")); \
	if ( can_color ) { \
		fprintf(file, color); \
	} \
	fprintf(file, __VA_ARGS__ ); \
	if ( can_color ) { \
		fprintf(file, "\033[0m"); \
	} \
})

#define MAX( a, b ) ({ typeof (a) a__ = (a); typeof (b) b__ = (b); (a__) < (b__) ? (b__) : (a__);})
#define MIN( a, b ) ({ typeof (a) a__ = (a); typeof (b) b__ = (b); (a__) > (b__) ? (b__) : (a__);})

#define P_ERR( ... ) ({P_COLOR( stderr, STDERR_FILENO, "\033[0;31m", "Error: "); fprintf(stderr, __VA_ARGS__); fflush(stderr);})
#define P_( ... ) ({P_COLOR( stdout, STDOUT_FILENO, "\033[0;32m", __VA_ARGS__); fflush(stdout);})
#define P_INCOMING_TRAFFIC( ... ) ({P_COLOR( stdout, STDOUT_FILENO, "\033[0;35m", __VA_ARGS__); fflush(stdout);})
#define P_OUTGOING_TRAFFIC( ... ) ({P_COLOR( stdout, STDOUT_FILENO, "\033[0;36m", __VA_ARGS__); fflush(stdout);})

#ifndef NDEBUG
#define P_D( ... ) ({P_COLOR( stderr, STDERR_FILENO, "\033[0;32m", "Debug: "); fprintf(stderr, __VA_ARGS__); fflush(stderr);})
#else
#define P_D( ... ) ({0;})
#endif

#define SOCKETBUFFERSIZE ((size_t)8 * 1024)

struct connection_socket {
	int socket;
	unsigned char * recv_buffer;
	size_t recv_buffer_capacity;
	size_t received_bytes;
	size_t write_offset_bytes;
	char * name;
};

struct connection {
	struct connection *next;
	struct addrinfo *remote_addrinfo;
	enum {
		kConnectionState_Unknown,
		kConnectionState_Connect,
		kConnectionState_Idle,
		kConnectionState_Error,
	} state;

	struct connection_socket incoming;
	struct connection_socket outgoing;

};

void connection_free( struct connection *connection );
char * create_readable_address( struct sockaddr *address, socklen_t address_len );
int set_socket_parameters( int socket );
int connection_socket_recv( struct connection_socket *connection_socket );
int connection_socket_send( struct connection_socket *connection_socket, struct connection_socket *connection_socket_out );
int copy_buffer_to_log_buffer( unsigned char const * const buffer, size_t buffer_len, unsigned char * const log_buffer, size_t log_buffer_len );

int main( int argc, char **argv) {
	int e = 0;
	
	enum command_line_options {
		command_line_remote = 1,
		command_line_local,
		command_line_remote_port,
		command_line_local_port,
		command_line_log_stdout,
		command_line_log_stdout_off,
		command_line_user,
		command_line_group,
		command_line_help,
	};

	struct option const command_line_options[] = {
		{
			.name = "remote-address",
			.has_arg = optional_argument,
			.flag = NULL,
			.val = command_line_remote,
		},
		{
			.name = "remote-port",
			.has_arg = optional_argument,
			.flag = NULL,
			.val = command_line_remote_port,
		},
		{
			.name = "local-address",
			.has_arg = optional_argument,
			.flag = NULL,
			.val = command_line_local,
		},
		{
			.name = "local-port",
			.has_arg = optional_argument,
			.flag = NULL,
			.val = command_line_local_port,
		},
		{
			.name = "log",
			.has_arg = optional_argument,
			.flag = NULL,
			.val = command_line_log_stdout,
		},
		{
			.name = "no-log",
			.has_arg = optional_argument,
			.flag = NULL,
			.val = command_line_log_stdout_off,
		},
		{
			.name = "user",
			.has_arg = optional_argument,
			.flag = NULL,
			.val = command_line_user,
		},
		{
			.name = "group",
			.has_arg = optional_argument,
			.flag = NULL,
			.val = command_line_group,
		},
		{
			.name = "help",
			.has_arg = optional_argument,
			.flag = NULL,
			.val = command_line_help,
		},
		{ /* empty */},
	};
	
	char * remote_address = NULL;
	char * local_address = NULL;
	char * remote_port = NULL;
	char * local_port = NULL;
	char * user_name = NULL;
	char * group_name = NULL;
	int log_network_traffic = 1;
	int print_help = 0;

	for ( enum command_line_options val = 0; (-1 != (val = getopt_long_only(argc, argv, "", command_line_options, NULL)) ); ) {
		switch ( val ) {
			case command_line_remote:
				remote_address = optarg;
				break;
			case command_line_local:
				local_address = optarg;
				break;
			case command_line_remote_port:
				remote_port = optarg;
				break;
			case command_line_local_port:
				local_port = optarg;
				break;
			case command_line_log_stdout:
				log_network_traffic = 1;
				break;
			case command_line_log_stdout_off:
				log_network_traffic = 0;
				break;
			case command_line_user:
				user_name = optarg;
				break;
			case command_line_group:
				group_name = optarg;
				break;
			case command_line_help:
				print_help = 1;
				break;
			default:
				e = 1;
				print_help = 1;
				break;
		}
	}

	int print_help_save_e = e;

	if ( print_help ) {
		e = 1;
	}
	
	if ( !e && NULL == remote_address ) {
		e = 1;
		P_ERR( "Missing value to argument --remote-address\n");
	}
	
	if ( !e && NULL == remote_port ) {
		e = 1;
		P_ERR( "Missing value to argument --remote-port\n");
	}
	
	struct addrinfo const addrinfo_hints = {
		.ai_family = PF_UNSPEC,
		.ai_socktype = SOCK_STREAM,
		.ai_protocol = IPPROTO_TCP,
		.ai_flags = AI_ADDRCONFIG,
	};
	struct addrinfo *addrinfo_remote_results = NULL;
	struct addrinfo *addrinfo_local_results = NULL;

	if ( !e && (local_address || local_port) ) {
		e = getaddrinfo(local_address, local_port, &addrinfo_hints, &addrinfo_local_results);
		if ( e ) {
			P_ERR( "Failed to lookup local address.\n");
			if ( local_address )P_ERR( "Address: %s\n", local_address);
			if ( local_port )P_ERR( "Port: %s\n", local_port);
			P_ERR( "getaddrinfo() : %s\n", gai_strerror(e));
		}
		e = e ? e : (NULL == addrinfo_local_results);
	}
	
	int listen_socket = -1;
	if ( !e ) {
		int domain = PF_INET6;
		int type = SOCK_STREAM;
		int protocol = 0;
		
		if ( addrinfo_local_results ) {
			domain = addrinfo_local_results->ai_family;
			type = addrinfo_local_results->ai_socktype;
			protocol = addrinfo_local_results->ai_protocol;
		}
		
		listen_socket = socket(domain, type, protocol);
		if ( -1 == listen_socket ) {
			e = 1;
			P_ERR( "Failed to create socket for listening: %s\n", strerror(errno));
		}
	}

	if ( !e ) {
		set_socket_parameters( listen_socket );
	}

	if ( !e ) {
		void *address = NULL;
		socklen_t address_len = 0;
		struct sockaddr_in6 anyaddr = {
			.sin6_addr = in6addr_any,
			.sin6_family = AF_INET6,
		};

		if ( addrinfo_local_results ) {
			address = addrinfo_local_results->ai_addr;
			address_len = addrinfo_local_results->ai_addrlen;
		} else {
			address = &anyaddr;
			address_len = sizeof(anyaddr);
		}

		if ( 0 != bind( listen_socket, address, address_len) ) {
			e = 1;
			P_ERR( "Failed to bind socket for listening: %s\n", strerror(errno));
		}
	}

	char * listen_socket_name = NULL;
	if ( !e ) {
		if ( 0 != listen( listen_socket, 8) ) {
			e = 1;
			P_ERR( "Failed to listen to socket for listening: %s\n", strerror(errno));
		} else {
			struct sockaddr address = {};
			socklen_t address_len = sizeof(address);
			if ( 0 == getsockname( listen_socket, &address, &address_len) ) {
				listen_socket_name = create_readable_address( &address, address_len);
				P_( "Listening on %s.\n", listen_socket_name ? listen_socket_name : "unknown");
			}
		}
	}

	if ( ! e && group_name ) {
		struct group *result;
		struct group result_group;
		char buffer[1024];
		if ( 0 == getgrnam_r( group_name, &result_group, buffer, sizeof(buffer), &result ) ) {
			if ( NULL != result ) {
				if ( 0 == setregid( result->gr_gid, result->gr_gid) ) {
					P_( "Changing gid to %"PRIdMAX".\n", (intmax_t)result->gr_gid);
				} else {
					e = 1;
					P_ERR( "Failed to change process group: %s\n", strerror(errno));
				}
			} else {
				P_ERR( "Could not find group: %s\n", group_name);
				e = 1;
			}
		}
	}

	if ( ! e && user_name ) {
		struct passwd *result;
		struct passwd result_passwd;
		char buffer[1024];
		if ( 0 == getpwnam_r( user_name, &result_passwd, buffer, sizeof(buffer), &result ) ) {
			if ( NULL != result ) {
				if ( 0 == setreuid( result->pw_uid, result->pw_uid) ) {
					P_( "Changing uid to %"PRIdMAX".\n", (intmax_t)result->pw_uid);
				} else {
					e = 1;
					P_ERR( "Failed to change process user: %s\n", strerror(errno));
				}
			} else {
				P_ERR( "Could not find user: %s\n", user_name);
				e = 1;
			}
		}
	}

	if ( ! e ) {
		if ( 0 == getuid() ) {
			e = 1;
		}

		if ( 0 == geteuid() ) {
			e = 1;
		}
		
		if ( 0 == getgid() ) {
			e = 1;
		}

		if ( 0 == getegid() ) {
			e = 1;
		}

		if ( e ) {
			P_ERR( "Should not run with super user priviliges.\n");
			if ( 0 == getuid() || 0 == geteuid() ) {
				P_ERR( "Change user with --user=[user].\n");
			}

			if ( 0 == getgid() || 0 == getegid() ) {
				P_ERR( "Change group with --group=[group].\n");
			}
		}
	}

	if ( !e ) {
		e = getaddrinfo(remote_address, remote_port, &addrinfo_hints, &addrinfo_remote_results);
		if ( e ) {
			P_ERR( "Failed to lookup remote address.\n");
			if ( remote_address )P_ERR( "Address: %s\n", remote_address);
			if ( remote_port )P_ERR( "Port: %s\n", remote_port);
			P_ERR( "getaddrinfo() : %s\n", gai_strerror(e));
		}
		e = e ? e : (NULL == addrinfo_remote_results);
	}

	
	unsigned char *log_buffer = NULL;
	size_t log_buffer_capacity = 0;
	struct connection * connections = NULL;

	if ( log_network_traffic ) {
		size_t capacity = MIN( SIZE_MAX - 1, SOCKETBUFFERSIZE ) + 1;
		log_buffer = malloc(capacity);
		if ( log_buffer ) {
			log_buffer_capacity = capacity;
		}
	}

	while ( !e ) {
		fd_set read_fd_set;
		fd_set write_fd_set;
		fd_set error_fd_set;
		FD_ZERO(&read_fd_set);
		FD_ZERO(&write_fd_set);
		FD_ZERO(&error_fd_set);
		
		int max_fd = -1;
		{ // accept
			FD_SET( listen_socket, &read_fd_set );
			max_fd = MAX(max_fd, listen_socket);
		}
		
		for ( struct connection **connectionlist = &connections; !e && *connectionlist; ) {
			struct connection *connection = *connectionlist;

			switch ( connection->state ) {
				default:
					e = 1;
					P_ERR( "Unknown connection state: %d\n", connection->state);
					break;
				case kConnectionState_Connect: {
					if ( -1 == connection->outgoing.socket ) {
						if ( connection->remote_addrinfo ) {
							int outgoing_socket = socket(connection->remote_addrinfo->ai_family, connection->remote_addrinfo->ai_socktype, connection->remote_addrinfo->ai_protocol);
							if ( -1 == outgoing_socket ) {
								connection->state = kConnectionState_Error;
								P_ERR( "Could not create socket to remote host: %s", strerror(errno));
							} else {
								set_socket_parameters( outgoing_socket );

								if ( connection->outgoing.name ) {
									free( connection->outgoing.name );
									connection->outgoing.name = NULL;
								}
								connection->outgoing.name = create_readable_address( connection->remote_addrinfo->ai_addr, connection->remote_addrinfo->ai_addrlen );

								if ( 0 == connect( outgoing_socket, connection->remote_addrinfo->ai_addr, connection->remote_addrinfo->ai_addrlen) ) {
									
								} else {
									switch ( errno ) {
										case EINTR:
											close( outgoing_socket );
											outgoing_socket = -1;
											break;
										case EINPROGRESS:
											break;
										default:
											P_ERR( "Could not connect to remote host: %s\n", strerror(errno));
											connection->state = kConnectionState_Error;
											close( outgoing_socket );
											outgoing_socket = -1;
											break;
									}
								}
								
								if ( -1 != outgoing_socket ) {
									connection->outgoing.socket = outgoing_socket;
								}
							}

						} else {
							connection->state = kConnectionState_Error;
						}
					}
					
					if ( -1 != connection->outgoing.socket ) {
						FD_SET( connection->outgoing.socket, &write_fd_set );
					}
				} break;
				case kConnectionState_Error: {
					*connectionlist = connection->next;
					connection_free(connection);
					connection = NULL;
				} break;
				case kConnectionState_Idle: {
					if ( connection->incoming.recv_buffer && 0 < connection->incoming.received_bytes ) {
						FD_SET( connection->outgoing.socket, &write_fd_set );
					} else {
						FD_SET( connection->incoming.socket, &read_fd_set );
					}
					
					if ( connection->outgoing.recv_buffer && 0 < connection->outgoing.received_bytes ) {
						FD_SET( connection->incoming.socket, &write_fd_set );
					} else {
						FD_SET( connection->outgoing.socket, &read_fd_set );
					}

					
				} break;
			}
			
			if ( connection ) {
				if ( -1 != connection->incoming.socket ) {
					FD_SET( connection->incoming.socket, &error_fd_set );
					max_fd = MAX(max_fd, connection->incoming.socket);
				}
				if ( -1 != connection->outgoing.socket ) {
					FD_SET( connection->outgoing.socket, &error_fd_set );
					max_fd = MAX(max_fd, connection->outgoing.socket);
				}
				connectionlist = &connection->next;
			}
		}
		
		if ( ! e ) {
			e = ( -1 == max_fd );
			if ( e ) {
				P_ERR( "No active connections.\n");
			}
		}

		if ( e ) {
			// Previous error
		} else if ( -1 != select( max_fd + 1, &read_fd_set, &write_fd_set, &error_fd_set, NULL) ) {

			if ( FD_ISSET( listen_socket, &read_fd_set) ) {
				struct sockaddr address = {};
				socklen_t address_len = sizeof(address);
				int incoming_socket = accept( listen_socket, &address, &address_len);
				if ( -1 == incoming_socket ) {
					P_ERR( "Failed to accept socket for incoming connection: %s\n", strerror(errno));
				} else {
					set_socket_parameters( incoming_socket );

					
					struct connection * conn = calloc(1, sizeof(*conn));
					conn->incoming.socket = incoming_socket;
					conn->incoming.name = create_readable_address(&address, address_len);
					conn->outgoing.socket = -1;
					conn->remote_addrinfo = addrinfo_remote_results;
					conn->state = kConnectionState_Connect;
					
					conn->next = connections;
					connections = conn;

					P_( "(%s) Incoming connection from %s.\n", listen_socket_name ? listen_socket_name : "unknown", conn->incoming.name ? conn->incoming.name : "unknown");
				}
			}
			
			for ( struct connection **connectionlist = &connections; !e && *connectionlist; connectionlist = &((**connectionlist)).next ) {
				struct connection *connection = *connectionlist;

				switch ( connection->state ) {
					default: {
						e = 1;
						P_ERR( "Unknown activity on connection state: %d\n", connection->state);
					} break;
					case kConnectionState_Connect: {
						if ( -1 != connection->outgoing.socket ) {
							if ( FD_ISSET( connection->outgoing.socket, &write_fd_set ) ) {
								int connect_result = 0;
								socklen_t connect_result_len = sizeof(connect_result);
								if ( 0 == getsockopt( connection->outgoing.socket, SOL_SOCKET, SO_ERROR, &connect_result, &connect_result_len) ) {

									if ( 0 == connect_result ) {
										connection->state = kConnectionState_Idle;
										P_( "(%s) Connected to %s.\n", connection->incoming.name ? connection->incoming.name : "unknown", connection->outgoing.name ? connection->outgoing.name : "unknown");
									} else {
										char *errorstr = strerror(connect_result);
										P_ERR( "(%s) Connect failed: %s\n", connection->incoming.name ? connection->incoming.name : "unknown", errorstr);

										close( connection->outgoing.socket );
										connection->outgoing.socket = -1;

										if ( connection->remote_addrinfo->ai_next ) {
											connection->remote_addrinfo = connection->remote_addrinfo->ai_next;
										} else {
											connection->state = kConnectionState_Error;
										}
									}
								} else {
									connection->state = kConnectionState_Error;
								}
							}
						}
					} break;
					case kConnectionState_Idle: {
						if ( FD_ISSET( connection->incoming.socket, &read_fd_set ) ) {
							if ( 0 == connection_socket_recv( &connection->incoming) ) {
								if ( log_network_traffic ) {
									copy_buffer_to_log_buffer( connection->incoming.recv_buffer, connection->incoming.received_bytes, log_buffer, log_buffer_capacity);
									P_INCOMING_TRAFFIC("%s", log_buffer);
								}
							} else {
								connection->state = kConnectionState_Error;
							}
						}

						if ( FD_ISSET( connection->outgoing.socket, &read_fd_set ) ) {
							if ( 0 == connection_socket_recv( &connection->outgoing) ) {
								if ( log_network_traffic ) {
									copy_buffer_to_log_buffer( connection->outgoing.recv_buffer, connection->outgoing.received_bytes, log_buffer, log_buffer_capacity);
									P_OUTGOING_TRAFFIC("%s", log_buffer);
								}
							} else {
								connection->state = kConnectionState_Error;
							}
						}

						if ( FD_ISSET( connection->incoming.socket, &write_fd_set ) ) {
							if ( 0 != connection_socket_send( &connection->outgoing, &connection->incoming) ) {
								connection->state = kConnectionState_Error;
							}
						}

						if ( FD_ISSET( connection->outgoing.socket, &write_fd_set ) ) {
							if ( 0 != connection_socket_send( &connection->incoming, &connection->outgoing) ) {
								connection->state = kConnectionState_Error;
							}
						}

					} break;
				}
				
				if ( -1 != connection->outgoing.socket ) {
					if ( FD_ISSET( connection->outgoing.socket, &error_fd_set ) ) {
						int connect_result = 0;
						socklen_t connect_result_len = sizeof(connect_result);
						if ( 0 == getsockopt( connection->outgoing.socket, SOL_SOCKET, SO_ERROR, &connect_result, &connect_result_len) ) {
							char *errorstr = strerror(connect_result);
							P_ERR( "(%s) Socket broke: %s\n", connection->outgoing.name ? connection->outgoing.name : "unknown", errorstr);
						}
						connection->state = kConnectionState_Error;
					}
				}
				
				if ( -1 != connection->incoming.socket ) {
					if ( FD_ISSET( connection->incoming.socket, &error_fd_set ) ) {
						int connect_result = 0;
						socklen_t connect_result_len = sizeof(connect_result);
						if ( 0 == getsockopt( connection->incoming.socket, SOL_SOCKET, SO_ERROR, &connect_result, &connect_result_len) ) {
							char *errorstr = strerror(connect_result);
							P_ERR( "(%s) Socket broke: %s\n", connection->incoming.name ? connection->incoming.name : "unknown", errorstr);
						}
						connection->state = kConnectionState_Error;
					}
				}
			}
		} else {
			switch (errno) {
				case EINTR:
					break;
				default:
					e = 1;
					P_ERR( "Failed waiting for network trafic, select(): %s\n", strerror(errno));
					break;
			}
		}
	}

	if ( print_help ) {
		P_("mmtt is a tcp-connection forwarder.\nUsage:\n\n");

		for ( size_t i = 0; i < sizeof(command_line_options) / sizeof(command_line_options[0]); ++i ) {
			if ( command_line_options[i].name ) {
				char const *description = NULL;
				char const *argument = NULL;
				switch ( (enum command_line_options)command_line_options[i].val ) {
					case command_line_help:
						description = "print this help text";
						break;
					case command_line_user:
						description = "run as specified user";
						argument = "[username]";
						break;
					case command_line_group:
						description = "run as specified group";
						argument = "[groupname]";
						break;
					case command_line_log_stdout:
						description = "enable logging";
						break;
					case command_line_log_stdout_off:
						description = "disable logging";
						break;
					case command_line_remote:
						description = "address to forward connections to";
						argument = "[address]";
						break;
					case command_line_remote_port:
						description = "port to forward connections to";
						argument = "[port]";
						break;
					case command_line_local:
						description = "listen for connections on a specific address";
						argument = "[address]";
						break;
					case command_line_local_port:
						description = "listen for connections on a specific port";
						argument = "[port]";
						break;
					default:
						break;
				}
				int arglen = strlen(command_line_options[i].name);
				for ( int i = arglen; i < 16; ++i ) {
					fprintf(stdout," ");
				}
				P_("--%s  %-13s%s\n", command_line_options[i].name, argument ? argument : "", description ? description : "");
			}
		}
		e = print_help_save_e;
	}
	
	
	while ( connections ) {
		struct connection * const head = connections;
		connections = head->next;
		connection_free(head);
	}
	
	if ( -1 != listen_socket ) close(listen_socket);
	if ( addrinfo_remote_results ) freeaddrinfo(addrinfo_remote_results);
	if ( addrinfo_local_results ) freeaddrinfo(addrinfo_local_results);
	if ( log_buffer ) free(log_buffer);
	if ( listen_socket_name) free(listen_socket_name);

	return e;
}

void connection_free( struct connection *connection ) {
	if ( connection->incoming.recv_buffer ) free( connection->incoming.recv_buffer );
	if ( connection->outgoing.recv_buffer ) free( connection->outgoing.recv_buffer );
	if ( connection->incoming.name ) free( connection->incoming.name );
	if ( connection->outgoing.name ) free( connection->outgoing.name );
	if ( -1 != connection->incoming.socket ) close( connection->incoming.socket );
	if ( -1 != connection->outgoing.socket ) close( connection->outgoing.socket );
	free(connection);
}

char * create_readable_address( struct sockaddr *address, socklen_t address_len ) {
	char * address_string = NULL;
	char buffer[1024];
	char const *printf_format = NULL;
	size_t port = 0;
	switch ( address->sa_family ) {
		case AF_INET: {
			struct sockaddr_in * address4 = (void *)address;
			if ( NULL != inet_ntop( address->sa_family, &address4->sin_addr, buffer, sizeof(buffer) - 1) ) {
				port = ntohs(address4->sin_port);
				printf_format = "%s:%jd";
			}
		} break;
		case AF_INET6: {
			struct sockaddr_in6 * address6 = (void *)address;
			if ( NULL != inet_ntop( address->sa_family, &address6->sin6_addr, buffer, sizeof(buffer) - 1) ) {
				port = ntohs(address6->sin6_port);
				printf_format = "[%s]:%jd";
			}
		} break;
		default: {
		} break;
	}

	if ( printf_format ) {
		int const string_length = snprintf(NULL, 0, printf_format, buffer, port);
		if ( 0 < string_length ) {
			size_t const string_buffer_capacity = MIN(SIZE_MAX - 1, string_length) + 1;
			address_string = malloc( string_buffer_capacity );
			if ( address_string ) {
				snprintf(address_string, string_buffer_capacity, printf_format, buffer, port);
			}
		}
	}

	return address_string;
}

int copy_buffer_to_log_buffer( unsigned char const * const buffer, size_t buffer_len, unsigned char * const log_buffer, size_t log_buffer_len ) {
	if ( 0 < log_buffer_len )
	{
		size_t len = MIN(buffer_len, log_buffer_len - 1);
		for ( size_t i = 0; i < len; ++i ) {
			unsigned char c = buffer[i];
			if ( c < ' ' ) {
				switch ( c ) {
					default:
						c = '.';
						break;
					case '\n':
					case '\r':
					case '\t':
						break;
				}
			}
			log_buffer[i] = c;
		}
		log_buffer[len] = 0;
	}
	return 0;
}

int set_socket_parameters( int socket ) {
	{
		unsigned flags = 0;
		if ( -1 != (flags = fcntl(socket, F_GETFL ) ) ) {
			flags |= O_NONBLOCK;
			fcntl(socket, F_SETFL, flags);
		}
	}

	int socketoption_enable = 1;
	setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &socketoption_enable, sizeof(socketoption_enable));
	setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, &socketoption_enable, sizeof(socketoption_enable));

	return 0;
}

int connection_socket_recv( struct connection_socket *connection_socket ) {
	int e = 0;
	
	if ( NULL == connection_socket->recv_buffer ) {
		connection_socket->recv_buffer_capacity = SOCKETBUFFERSIZE;
		connection_socket->recv_buffer = malloc(connection_socket->recv_buffer_capacity);
		connection_socket->received_bytes = 0;
	}

	ssize_t res = recv( connection_socket->socket, connection_socket->recv_buffer, connection_socket->recv_buffer_capacity, 0);
	if ( 0 > res ) {
		switch ( errno ) {
			case EAGAIN: {
			} break;
			case EINTR: {
			} break;
			default: {
				P_ERR( "(%s) Error recv from socket: %s\n", connection_socket->name ? connection_socket->name : "unknown", strerror(errno));
				e = 1;
			} break;
		}
	} else if ( 0 == res ) {
		P_( "(%s) Socket closed.\n", connection_socket->name ? connection_socket->name : "unknown");
		e = 1;
	} else {
		connection_socket->received_bytes = res;
	}
	return e;
}

int connection_socket_send( struct connection_socket *connection_socket, struct connection_socket *connection_socket_out ) {
	int e = 0;
	if ( connection_socket->write_offset_bytes < connection_socket->received_bytes ) {
		ssize_t res = send( connection_socket_out->socket, connection_socket->recv_buffer + connection_socket->write_offset_bytes, connection_socket->received_bytes - connection_socket->write_offset_bytes, 0);

		if ( 0 > res ) {
			switch ( errno ) {
				case EAGAIN: {
				} break;
				case EINTR: {
				} break;
				default: {
					P_ERR( "(%s) Error send to socket: %s\n", connection_socket_out->name ? connection_socket_out->name : "unknown", strerror(errno));
					e = 1;
				} break;
			}
		} else {
			connection_socket->write_offset_bytes += res;
		}
	}

	if ( !e && connection_socket->write_offset_bytes >= connection_socket->received_bytes ) {
		connection_socket->write_offset_bytes = 0;
		connection_socket->received_bytes = 0;
	}

	return e;
}

