/* udp.cpp - the socket layer for the CAS BACnet Stack Quick Start (quick-start.cpp). Not part of the stack.

   A minimal non-blocking UDP socket for POSIX and Windows. UdpOpen finds this host's IPv4 address by asking
   the operating system which local address it would use to reach the internet (no packet is sent), and
   assumes a /24 subnet mask. Set BACNET_IP_ADDRESS and BACNET_IP_SUBNET_MASK in the environment to choose
   them yourself, for example on a host with several network interfaces. */

#if defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
#include <winsock2.h>
#include <ws2tcpip.h>
#if defined(_MSC_VER)
#pragma comment(lib, "ws2_32.lib")
#endif
typedef SOCKET SocketHandle;
static const SocketHandle INVALID_SOCKET_HANDLE = INVALID_SOCKET;
#define CloseSocket closesocket
#else
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
typedef int SocketHandle;
static const SocketHandle INVALID_SOCKET_HANDLE = -1;
#define CloseSocket close
#endif
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static SocketHandle g_socket = INVALID_SOCKET_HANDLE;
static uint8_t g_ownAddress[4];
static uint16_t g_ownPort;

static bool FindLocalAddress(uint8_t ipAddress[4])
{
	SocketHandle probe = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (probe == INVALID_SOCKET_HANDLE) {
		return false;
	}
	struct sockaddr_in remote;
	memset(&remote, 0, sizeof(remote));
	remote.sin_family = AF_INET;
	remote.sin_port = htons(53);
	inet_pton(AF_INET, "8.8.8.8", &remote.sin_addr);
	struct sockaddr_in local;
	socklen_t localLength = sizeof(local);
	const bool found = connect(probe, (struct sockaddr*)&remote, sizeof(remote)) == 0 &&
		getsockname(probe, (struct sockaddr*)&local, &localLength) == 0;
	if (found) {
		memcpy(ipAddress, &local.sin_addr, 4);
	}
	CloseSocket(probe);
	return found;
}

bool UdpOpen(uint16_t port, uint8_t ipAddress[4], uint8_t subnetMask[4])
{
#if defined(_WIN32)
	WSADATA winsockData;
	if (WSAStartup(MAKEWORD(2, 2), &winsockData) != 0) {
		return false;
	}
#endif
	const char* configuredAddress = getenv("BACNET_IP_ADDRESS");
	if (configuredAddress != NULL) {
		if (inet_pton(AF_INET, configuredAddress, ipAddress) != 1) {
			fprintf(stderr, "BACNET_IP_ADDRESS is not an IPv4 address: %s\n", configuredAddress);
			return false;
		}
	} else if (!FindLocalAddress(ipAddress)) {
		fprintf(stderr, "Cannot find this host's IPv4 address; set BACNET_IP_ADDRESS.\n");
		return false;
	}
	const char* configuredMask = getenv("BACNET_IP_SUBNET_MASK");
	const uint8_t defaultMask[4] = { 255, 255, 255, 0 };
	if (configuredMask == NULL || inet_pton(AF_INET, configuredMask, subnetMask) != 1) {
		memcpy(subnetMask, defaultMask, 4);
	}

	g_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (g_socket == INVALID_SOCKET_HANDLE) {
		return false;
	}
	int enable = 1;
	setsockopt(g_socket, SOL_SOCKET, SO_BROADCAST, (const char*)&enable, sizeof(enable));
	setsockopt(g_socket, SOL_SOCKET, SO_REUSEADDR, (const char*)&enable, sizeof(enable));
#if defined(_WIN32)
	u_long nonBlocking = 1;
	ioctlsocket(g_socket, FIONBIO, &nonBlocking);
#else
	fcntl(g_socket, F_SETFL, fcntl(g_socket, F_GETFL, 0) | O_NONBLOCK);
#endif
	struct sockaddr_in local;
	memset(&local, 0, sizeof(local));
	local.sin_family = AF_INET;
	local.sin_port = htons(port);
	local.sin_addr.s_addr = htonl(INADDR_ANY);
	if (bind(g_socket, (struct sockaddr*)&local, sizeof(local)) != 0) {
		fprintf(stderr, "Cannot bind UDP port %u.\n", (unsigned int)port);
		return false;
	}
	memcpy(g_ownAddress, ipAddress, 4);
	g_ownPort = port;
	printf("BACnet/IP on %u.%u.%u.%u:%u\n", ipAddress[0], ipAddress[1], ipAddress[2], ipAddress[3], (unsigned int)port);
	return true;
}

uint16_t UdpReceive(uint8_t* buffer, uint16_t bufferLength, uint8_t fromAddress[4], uint16_t* fromPort)
{
	struct sockaddr_in from;
	socklen_t fromLength = sizeof(from);
	const int bytesRead = (int)recvfrom(g_socket, (char*)buffer, bufferLength, 0, (struct sockaddr*)&from, &fromLength);
	if (bytesRead <= 0) {
		return 0; /* Nothing waiting: the socket does not block. */
	}
	memcpy(fromAddress, &from.sin_addr, 4);
	*fromPort = ntohs(from.sin_port);
	/* Discard this device's own broadcasts, which come back to the socket that sent them. */
	if (memcmp(fromAddress, g_ownAddress, 4) == 0 && *fromPort == g_ownPort) {
		return 0;
	}
	return (uint16_t)bytesRead;
}

uint16_t UdpSend(const uint8_t toAddress[4], uint16_t toPort, const uint8_t* data, uint16_t length)
{
	struct sockaddr_in to;
	memset(&to, 0, sizeof(to));
	to.sin_family = AF_INET;
	to.sin_port = htons(toPort);
	memcpy(&to.sin_addr, toAddress, 4);
	const int bytesSent = (int)sendto(g_socket, (const char*)data, length, 0, (struct sockaddr*)&to, sizeof(to));
	return bytesSent < 0 ? 0 : (uint16_t)bytesSent;
}
