//
// Created by steven on 10/27/2024.
//

#include <winsock2.h>
#include <Windows.h>
#include <ws2ipdef.h>
#include <ws2tcpip.h>
#include <iphlpapi.h>
#include <bcrypt.h>
#include <wincrypt.h>
#include <sysinfoapi.h>
#include <winternl.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include "wireguard.h"

static WIREGUARD_CREATE_ADAPTER_FUNC *WireGuardCreateAdapter;
static WIREGUARD_OPEN_ADAPTER_FUNC *WireGuardOpenAdapter;
static WIREGUARD_CLOSE_ADAPTER_FUNC *WireGuardCloseAdapter;
static WIREGUARD_GET_ADAPTER_LUID_FUNC *WireGuardGetAdapterLUID;
static WIREGUARD_GET_RUNNING_DRIVER_VERSION_FUNC *WireGuardGetRunningDriverVersion;
static WIREGUARD_DELETE_DRIVER_FUNC *WireGuardDeleteDriver;
static WIREGUARD_SET_LOGGER_FUNC *WireGuardSetLogger;
static WIREGUARD_SET_ADAPTER_LOGGING_FUNC *WireGuardSetAdapterLogging;
static WIREGUARD_GET_ADAPTER_STATE_FUNC *WireGuardGetAdapterState;
static WIREGUARD_SET_ADAPTER_STATE_FUNC *WireGuardSetAdapterState;
static WIREGUARD_GET_CONFIGURATION_FUNC *WireGuardGetConfiguration;
static WIREGUARD_SET_CONFIGURATION_FUNC *WireGuardSetConfiguration;

static HMODULE
InitializeWireGuardNT(void)
{
    HMODULE WireGuardDll =
            LoadLibraryExW(L"wireguard.dll", NULL, LOAD_LIBRARY_SEARCH_APPLICATION_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32);
    if (!WireGuardDll)
        return NULL;
#define X(Name) ((*(FARPROC *)&Name = GetProcAddress(WireGuardDll, #Name)) == NULL)
    if (X(WireGuardCreateAdapter) || X(WireGuardOpenAdapter) || X(WireGuardCloseAdapter) ||
        X(WireGuardGetAdapterLUID) || X(WireGuardGetRunningDriverVersion) || X(WireGuardDeleteDriver) ||
        X(WireGuardSetLogger) || X(WireGuardSetAdapterLogging) || X(WireGuardGetAdapterState) ||
        X(WireGuardSetAdapterState) || X(WireGuardGetConfiguration) || X(WireGuardSetConfiguration))
#undef X
    {
        DWORD LastError = GetLastError();
        FreeLibrary(WireGuardDll);
        SetLastError(LastError);
        return NULL;
    }
    return WireGuardDll;
}

static void CALLBACK
ConsoleLogger(_In_ WIREGUARD_LOGGER_LEVEL Level, _In_ DWORD64 Timestamp, _In_z_ const WCHAR *LogLine)
{
    SYSTEMTIME SystemTime;
    FileTimeToSystemTime((FILETIME *)&Timestamp, &SystemTime);
    WCHAR LevelMarker;
    switch (Level)
    {
        case WIREGUARD_LOG_INFO:
            LevelMarker = L'+';
            break;
        case WIREGUARD_LOG_WARN:
            LevelMarker = L'-';
            break;
        case WIREGUARD_LOG_ERR:
            LevelMarker = L'!';
            break;
        default:
            return;
    }
    fwprintf(
            stderr,
            L"%04u-%02u-%02u %02u:%02u:%02u.%04u [%c] %s\n",
            SystemTime.wYear,
            SystemTime.wMonth,
            SystemTime.wDay,
            SystemTime.wHour,
            SystemTime.wMinute,
            SystemTime.wSecond,
            SystemTime.wMilliseconds,
            LevelMarker,
            LogLine);
}

static DWORD64 Now(VOID)
{
    LARGE_INTEGER Timestamp;
    NtQuerySystemTime(&Timestamp);
    return Timestamp.QuadPart;
}

static DWORD
LogError(_In_z_ const WCHAR *Prefix, _In_ DWORD Error)
{
    WCHAR *SystemMessage = NULL, *FormattedMessage = NULL;
    FormatMessageW(
            FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_MAX_WIDTH_MASK,
            NULL,
            HRESULT_FROM_SETUPAPI(Error),
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            (void *)&SystemMessage,
            0,
            NULL);
    FormatMessageW(
            FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_ARGUMENT_ARRAY |
            FORMAT_MESSAGE_MAX_WIDTH_MASK,
            SystemMessage ? L"%1: %3(Code 0x%2!08X!)" : L"%1: Code 0x%2!08X!",
            0,
            0,
            (void *)&FormattedMessage,
            0,
            (va_list *)(DWORD_PTR[]){ (DWORD_PTR)Prefix, (DWORD_PTR)Error, (DWORD_PTR)SystemMessage });
    if (FormattedMessage)
        ConsoleLogger(WIREGUARD_LOG_ERR, Now(), FormattedMessage);
    LocalFree(FormattedMessage);
    LocalFree(SystemMessage);
    return Error;
}

static DWORD
LogLastError(_In_z_ const WCHAR *Prefix)
{
    DWORD LastError = GetLastError();
    LogError(Prefix, LastError);
    SetLastError(LastError);
    return LastError;
}

static void
Log(_In_ WIREGUARD_LOGGER_LEVEL Level, _In_z_ const WCHAR *Format, ...)
{
    WCHAR LogLine[0x200];
    va_list args;
    va_start(args, Format);
    _vsnwprintf_s(LogLine, _countof(LogLine), _TRUNCATE, Format, args);
    va_end(args);
    ConsoleLogger(Level, Now(), LogLine);
}

_Must_inspect_result_
_Return_type_success_(return != FALSE)
static BOOL
GenerateKeyPair(
        _Out_writes_bytes_all_(WIREGUARD_KEY_LENGTH) BYTE PublicKey[WIREGUARD_KEY_LENGTH],
        _Out_writes_bytes_all_(WIREGUARD_KEY_LENGTH) BYTE PrivateKey[WIREGUARD_KEY_LENGTH])
{
    BCRYPT_ALG_HANDLE Algorithm;
    BCRYPT_KEY_HANDLE Key;
    NTSTATUS Status;
    struct
    {
        BCRYPT_ECCKEY_BLOB Header;
        BYTE Public[32];
        BYTE Unused[32];
        BYTE Private[32];
    } ExportedKey;
    ULONG Bytes;

    Status = BCryptOpenAlgorithmProvider(&Algorithm, BCRYPT_ECDH_ALGORITHM, NULL, 0);
    if (!NT_SUCCESS(Status))
        goto out;

    Status = BCryptSetProperty(
            Algorithm, BCRYPT_ECC_CURVE_NAME, (PUCHAR)BCRYPT_ECC_CURVE_25519, sizeof(BCRYPT_ECC_CURVE_25519), 0);
    if (!NT_SUCCESS(Status))
        goto cleanupProvider;

    Status = BCryptGenerateKeyPair(Algorithm, &Key, 255, 0);
    if (!NT_SUCCESS(Status))
        goto cleanupProvider;

    Status = BCryptFinalizeKeyPair(Key, 0);
    if (!NT_SUCCESS(Status))
        goto cleanupKey;

    Status = BCryptExportKey(Key, NULL, BCRYPT_ECCPRIVATE_BLOB, (PUCHAR)&ExportedKey, sizeof(ExportedKey), &Bytes, 0);
    if (!NT_SUCCESS(Status))
        goto cleanupKey;

    memcpy(PublicKey, ExportedKey.Public, WIREGUARD_KEY_LENGTH);
    memcpy(PrivateKey, ExportedKey.Private, WIREGUARD_KEY_LENGTH);
    SecureZeroMemory(&ExportedKey, sizeof(ExportedKey));

    cleanupKey:
    BCryptDestroyKey(Key);
    cleanupProvider:
    BCryptCloseAlgorithmProvider(Algorithm, 0);
    out:
    SetLastError(RtlNtStatusToDosError(Status));
    return NT_SUCCESS(Status);
}

static HANDLE QuitEvent;

static BOOL WINAPI
CtrlHandler(_In_ DWORD CtrlType)
{
    switch (CtrlType)
    {
        case CTRL_C_EVENT:
        case CTRL_BREAK_EVENT:
        case CTRL_CLOSE_EVENT:
        case CTRL_LOGOFF_EVENT:
        case CTRL_SHUTDOWN_EVENT:
            Log(WIREGUARD_LOG_INFO, L"Cleaning up and shutting down");
            SetEvent(QuitEvent);
            return TRUE;
    }
    return FALSE;
}

_Return_type_success_(return != FALSE)
static BOOL
TalkToDemoServer(
        _In_reads_bytes_(InputLength) const CHAR *Input,
        _In_ DWORD InputLength,
        _Out_writes_bytes_(*OutputLength) CHAR *Output,
        _Inout_ DWORD *OutputLength,
        _Out_ SOCKADDR_INET *ResolvedDemoServer)
{
    SOCKET Socket = INVALID_SOCKET;
    ADDRINFOW Hints = { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM, .ai_protocol = IPPROTO_TCP }, *Resolution;
    BOOL Ret = FALSE;

    if (GetAddrInfoW(L"demo.wireguard.com", L"42912", &Hints, &Resolution))
        return FALSE;
    for (ADDRINFOW *Candidate = Resolution; Candidate; Candidate = Candidate->ai_next)
    {
        if (Candidate->ai_family != AF_INET && Candidate->ai_family != AF_INET6)
            continue;
        Socket = socket(Candidate->ai_family, Candidate->ai_socktype, Candidate->ai_protocol);
        if (Socket == INVALID_SOCKET)
            goto cleanupResolution;
        if (connect(Socket, Candidate->ai_addr, (int)Candidate->ai_addrlen) == SOCKET_ERROR)
        {
            closesocket(Socket);
            Socket = INVALID_SOCKET;
        }
        memcpy(ResolvedDemoServer, Candidate->ai_addr, Candidate->ai_addrlen);
        break;
    }
    if (Socket == INVALID_SOCKET)
        goto cleanupResolution;
    if (send(Socket, Input, InputLength, 0) == SOCKET_ERROR)
        goto cleanupSocket;
    if ((*OutputLength = recv(Socket, Output, *OutputLength, 0)) == SOCKET_ERROR)
        goto cleanupSocket;
    Ret = TRUE;
    cleanupSocket:
    closesocket(Socket);
    cleanupResolution:
    FreeAddrInfoW(Resolution);
    return Ret;
}

int __cdecl main(void)
{
    DWORD LastError;
    WSADATA WsaData;
    if (WSAStartup(MAKEWORD(2, 2), &WsaData))
        return LogError(L"Failed to initialize Winsock", GetLastError());
    HMODULE WireGuard = InitializeWireGuardNT();
    if (!WireGuard)
    {
        LastError = LogError(L"Failed to initialize WireGuardNT", GetLastError());
        goto cleanupWinsock;
    }
    WireGuardSetLogger(ConsoleLogger);
    Log(WIREGUARD_LOG_INFO, L"WireGuardNT library loaded");

    struct
    {
        WIREGUARD_INTERFACE Interface;
        WIREGUARD_PEER DemoServer;
        WIREGUARD_ALLOWED_IP AllV4;
    } Config = {
            .Interface = {
                    .Flags = WIREGUARD_INTERFACE_HAS_PRIVATE_KEY,
                    .PeersCount = 1
            },
            .DemoServer = {
                    .Flags = WIREGUARD_PEER_HAS_PUBLIC_KEY | WIREGUARD_PEER_HAS_ENDPOINT,
                    .AllowedIPsCount = 1
            },
            .AllV4 = {
                    .AddressFamily = AF_INET
            }
    };

    Log(WIREGUARD_LOG_INFO, L"Generating keypair");
    BYTE PublicKey[WIREGUARD_KEY_LENGTH];
    if (!GenerateKeyPair(PublicKey, Config.Interface.PrivateKey))
    {
        LastError = LogError(L"Failed to generate keypair", GetLastError());
        goto cleanupWireGuard;
    }
    CHAR PublicKeyString[46] = { 0 };
    DWORD Bytes = sizeof(PublicKeyString);
    CryptBinaryToStringA(
            PublicKey, sizeof(PublicKey), CRYPT_STRING_BASE64 | CRYPT_STRING_NOCR, PublicKeyString, &Bytes);
    CHAR ServerResponse[256] = { 0 };
    Log(WIREGUARD_LOG_INFO, L"Talking to demo server");
    Bytes = sizeof(ServerResponse) - 1;
    if (!TalkToDemoServer(
            PublicKeyString, (DWORD)strlen(PublicKeyString), ServerResponse, &Bytes, &Config.DemoServer.Endpoint))
    {
        LastError = LogError(L"Failed to talk to demo server", GetLastError());
        goto cleanupWireGuard;
    }

    CHAR *Colon1 = strchr(ServerResponse, ':');
    CHAR *Colon2 = Colon1 ? strchr(Colon1 + 1, ':') : NULL;
    CHAR *Colon3 = Colon2 ? strchr(Colon2 + 1, ':') : NULL;
    if (!Colon1 || !Colon2 || !Colon3)
    {
        LastError = LogError(L"Failed to parse demo server response", ERROR_UNDEFINED_CHARACTER);
        goto cleanupWireGuard;
    }
    if (Bytes && ServerResponse[--Bytes] == '\n')
        ServerResponse[Bytes] = '\0';
    *Colon1 = *Colon2 = *Colon3 = '\0';

    MIB_UNICASTIPADDRESS_ROW AddressRow;
    InitializeUnicastIpAddressEntry(&AddressRow);
    AddressRow.Address.Ipv4.sin_family = AF_INET;
    AddressRow.OnLinkPrefixLength = 24; /* This is a /24 network */
    AddressRow.DadState = IpDadStatePreferred;
    Bytes = sizeof(Config.DemoServer.PublicKey);
    if (strcmp(ServerResponse, "OK") || InetPtonA(AF_INET, Colon3 + 1, &AddressRow.Address.Ipv4.sin_addr) != 1 ||
        !CryptStringToBinaryA(Colon1 + 1, 0, CRYPT_STRING_BASE64, Config.DemoServer.PublicKey, &Bytes, NULL, NULL))
    {
        LastError = LogError(L"Failed to parse demo server response", ERROR_UNDEFINED_CHARACTER);
        goto cleanupWireGuard;
    }
    if (Config.DemoServer.Endpoint.si_family == AF_INET)
        Config.DemoServer.Endpoint.Ipv4.sin_port = htons((u_short)atoi(Colon2 + 1));
    else if (Config.DemoServer.Endpoint.si_family == AF_INET6)
        Config.DemoServer.Endpoint.Ipv6.sin6_port = htons((u_short)atoi(Colon2 + 1));

    QuitEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
    if (!QuitEvent)
    {
        LastError = LogError(L"Failed to create event", GetLastError());
        goto cleanupWireGuard;
    }
    if (!SetConsoleCtrlHandler(CtrlHandler, TRUE))
    {
        LastError = LogError(L"Failed to set console handler", GetLastError());
        goto cleanupQuit;
    }

    GUID ExampleGuid = { 0xdeadc001, 0xbeef, 0xbabe, { 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef } };
    WIREGUARD_ADAPTER_HANDLE Adapter = WireGuardCreateAdapter(L"Demo", L"Example", &ExampleGuid);
    if (!Adapter)
    {
        LastError = GetLastError();
        LogError(L"Failed to create adapter", LastError);
        goto cleanupQuit;
    }

    if (!WireGuardSetAdapterLogging(Adapter, WIREGUARD_ADAPTER_LOG_ON))
        LogError(L"Failed to enable adapter logging", GetLastError());

    DWORD Version = WireGuardGetRunningDriverVersion();
    Log(WIREGUARD_LOG_INFO, L"WireGuardNT v%u.%u loaded", (Version >> 16) & 0xff, (Version >> 0) & 0xff);

    WireGuardGetAdapterLUID(Adapter, &AddressRow.InterfaceLuid);
    MIB_IPFORWARD_ROW2 DefaultRoute = { 0 };
    InitializeIpForwardEntry(&DefaultRoute);
    DefaultRoute.InterfaceLuid = AddressRow.InterfaceLuid;
    DefaultRoute.DestinationPrefix.Prefix.si_family = AF_INET;
    DefaultRoute.NextHop.si_family = AF_INET;
    DefaultRoute.Metric = 0;
    LastError = CreateIpForwardEntry2(&DefaultRoute);
    if (LastError != ERROR_SUCCESS && LastError != ERROR_OBJECT_ALREADY_EXISTS)
    {
        LogError(L"Failed to set default route", LastError);
        goto cleanupAdapter;
    }
    LastError = CreateUnicastIpAddressEntry(&AddressRow);
    if (LastError != ERROR_SUCCESS && LastError != ERROR_OBJECT_ALREADY_EXISTS)
    {
        LogError(L"Failed to set IP address", LastError);
        goto cleanupAdapter;
    }
    MIB_IPINTERFACE_ROW IpInterface = { 0 };
    InitializeIpInterfaceEntry(&IpInterface);
    IpInterface.InterfaceLuid = AddressRow.InterfaceLuid;
    IpInterface.Family = AF_INET;
    LastError = GetIpInterfaceEntry(&IpInterface);
    if (LastError != ERROR_SUCCESS)
    {
        LogError(L"Failed to get IP interface", LastError);
        goto cleanupAdapter;
    }
    IpInterface.UseAutomaticMetric = FALSE;
    IpInterface.Metric = 0;
    IpInterface.NlMtu = 1420;
    IpInterface.SitePrefixLength = 0;
    LastError = SetIpInterfaceEntry(&IpInterface);
    if (LastError != ERROR_SUCCESS)
    {
        LogError(L"Failed to set metric and MTU", LastError);
        goto cleanupAdapter;
    }

    Log(WIREGUARD_LOG_INFO, L"Setting configuration and adapter up");
    if (!WireGuardSetConfiguration(Adapter, &Config.Interface, sizeof(Config)) ||
        !WireGuardSetAdapterState(Adapter, WIREGUARD_ADAPTER_STATE_UP))
    {
        LastError = LogError(L"Failed to set configuration and adapter up", GetLastError());
        goto cleanupAdapter;
    }

    do
    {
        Bytes = sizeof(Config);
        if (!WireGuardGetConfiguration(Adapter, &Config.Interface, &Bytes) || !Config.Interface.PeersCount)
        {
            LastError = LogError(L"Failed to get configuration", GetLastError());
            goto cleanupAdapter;
        }
        DWORD64 Timestamp = Now();
        SYSTEMTIME SystemTime;
        FileTimeToSystemTime((FILETIME *)&Timestamp, &SystemTime);
        fwprintf(
                stderr,
                L"%04u-%02u-%02u %02u:%02u:%02u.%04u [#] RX: %llu, TX: %llu\r",
                SystemTime.wYear,
                SystemTime.wMonth,
                SystemTime.wDay,
                SystemTime.wHour,
                SystemTime.wMinute,
                SystemTime.wSecond,
                SystemTime.wMilliseconds,
                Config.DemoServer.RxBytes,
                Config.DemoServer.TxBytes);
    } while (WaitForSingleObject(QuitEvent, 1000) == WAIT_TIMEOUT);

    cleanupAdapter:
    WireGuardCloseAdapter(Adapter);
    cleanupQuit:
    SetConsoleCtrlHandler(CtrlHandler, FALSE);
    CloseHandle(QuitEvent);
    cleanupWireGuard:
    FreeLibrary(WireGuard);
    cleanupWinsock:
    WSACleanup();
    return LastError;
}