/*
 * PROJECT:         ReactOS IP Helper API
 * LICENSE:         LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later)
 * PURPOSE:         ICMP functions
 * COPYRIGHT:       2016 Tim Crawford (crawfxrd@gmail.com)
 *                  2019 Victor Perevertkin (victor.perevertkin@reactos.org)
 */

#include "iphlpapi_private.h"

WINE_DEFAULT_DEBUG_CHANNEL(iphlpapi);

HANDLE
WINAPI
Icmp6CreateFile(void)
{
    HANDLE IcmpFile;
    OBJECT_ATTRIBUTES ObjectAttributes;
    IO_STATUS_BLOCK IoStatusBlock;
    UNICODE_STRING DeviceName = RTL_CONSTANT_STRING(L"\\Device\\Ip6");
    NTSTATUS Status;

    InitializeObjectAttributes(
        &ObjectAttributes,
        &DeviceName,
        OBJ_CASE_INSENSITIVE,
        NULL,
        NULL);

    Status = NtCreateFile(
        &IcmpFile,
        GENERIC_EXECUTE,
        &ObjectAttributes,
        &IoStatusBlock,
        NULL,
        FILE_ATTRIBUTE_NORMAL,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        FILE_OPEN_IF,
        0,
        NULL,
        0);

    if (!NT_SUCCESS(Status))
    {
        SetLastError(RtlNtStatusToDosError(Status));
        return INVALID_HANDLE_VALUE;
    }

    return IcmpFile;
}

DWORD
WINAPI
Icmp6ParseReplies(
    _In_ LPVOID ReplyBuffer,
    _In_ DWORD  ReplySize)
{
    PICMPV6_ECHO_REPLY pEcho;

    if (ReplyBuffer == NULL || ReplySize == 0)
        return 0;

    pEcho = (PICMPV6_ECHO_REPLY)ReplyBuffer;

    // XXX: MSDN also says IP_TTL_EXPIRED_TRANSIT.
    if (pEcho->Status == IP_SUCCESS)
    {
        return 1;
    }

    SetLastError(pEcho->Status);
    return 0;
}

DWORD
WINAPI
Icmp6SendEcho2(
    _In_     HANDLE                 IcmpHandle,
    _In_opt_ HANDLE                 Event,
    _In_opt_ PIO_APC_ROUTINE        ApcRoutine,
    _In_opt_ PVOID                  ApcContext,
    _In_     struct sockaddr_in6    *SourceAddress,
    _In_     struct sockaddr_in6    *DestinationAddress,
    _In_     LPVOID                 RequestData,
    _In_     WORD                   RequestSize,
    _In_     PIP_OPTION_INFORMATION RequestOptions,
    _Out_    LPVOID                 ReplyBuffer,
    _In_     DWORD                  ReplySize,
    _In_     DWORD                  Timeout)
{
    HANDLE hEvent;
    PIO_STATUS_BLOCK IoStatusBlock;
    PVOID InputBuffer;
    ULONG InputBufferLength;
    //ULONG OutputBufferLength;
    PICMPV6_ECHO_REQUEST Request;
    NTSTATUS Status;

    InputBufferLength = sizeof(ICMPV6_ECHO_REQUEST) + RequestSize;

    if (ReplySize < sizeof(ICMPV6_ECHO_REPLY) + sizeof(IO_STATUS_BLOCK))
    {
        SetLastError(IP_BUF_TOO_SMALL);
        return 0;
    }

    // IO_STATUS_BLOCK will be stored inside ReplyBuffer (in the end)
    // that's because the function may return before device request ends
    IoStatusBlock = (PIO_STATUS_BLOCK)((PUCHAR)ReplyBuffer + ReplySize - sizeof(IO_STATUS_BLOCK));
    ReplySize -= sizeof(IO_STATUS_BLOCK);

    InputBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, InputBufferLength);
    if (InputBuffer == NULL)
    {
        SetLastError(ERROR_NOT_ENOUGH_MEMORY);
        return 0;
    }

    Request = (PICMPV6_ECHO_REQUEST)InputBuffer;

    Request->DestinationAddress.sin6_port = DestinationAddress->sin6_port;
    Request->DestinationAddress.sin6_flowinfo = DestinationAddress->sin6_flowinfo;
    CopyMemory(&(Request->DestinationAddress.sin6_addr), &(DestinationAddress->sin6_addr), sizeof(Request->DestinationAddress.sin6_addr));
    Request->DestinationAddress.sin6_scope_id = DestinationAddress->sin6_scope_id;

    Request->SourceAddress.sin6_port = SourceAddress->sin6_port;
    Request->SourceAddress.sin6_flowinfo = SourceAddress->sin6_flowinfo;
    CopyMemory(&(Request->SourceAddress.sin6_addr), &(SourceAddress->sin6_addr), sizeof(Request->SourceAddress.sin6_addr));
    Request->SourceAddress.sin6_scope_id = SourceAddress->sin6_scope_id;

    // XXX: What is this and why is it sometimes 0x72?
    Request->Unknown1 = 0x72;

    Request->Timeout = Timeout;
    Request->Ttl = RequestOptions->Ttl;
    Request->Flags = RequestOptions->Flags;

    if (RequestSize > 0)
    {
        CopyMemory((PBYTE)InputBuffer + sizeof(ICMPV6_ECHO_REQUEST), RequestData, RequestSize);
    }

    if (Event == NULL && ApcRoutine == NULL)
    {
        hEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
    }
    else
    {
        hEvent = Event;
    }

    Status = NtDeviceIoControlFile(
        IcmpHandle,
        hEvent,
        ApcRoutine,
        ApcContext,
        IoStatusBlock,
        IOCTL_ICMP_ECHO_REQUEST,
        InputBuffer,
        InputBufferLength,
        ReplyBuffer,
        ReplySize);         // TODO: Determine how Windows calculates OutputBufferLength.

    if (Event != NULL || ApcRoutine != NULL)
    {
        SetLastError(RtlNtStatusToDosError(Status));
        HeapFree(GetProcessHeap(), 0, InputBuffer);
        return 0;
    }

    if (Status == STATUS_PENDING)
    {
        Status = NtWaitForSingleObject(hEvent, FALSE, NULL);

        if (NT_SUCCESS(Status))
        {
            Status = IoStatusBlock->Status;
        }
    }

    CloseHandle(hEvent);
    HeapFree(GetProcessHeap(), 0, InputBuffer);

    if (!NT_SUCCESS(Status))
    {
        SetLastError(RtlNtStatusToDosError(Status));
        return 0;
    }

    Status = ((PICMPV6_ECHO_REPLY)ReplyBuffer)->Status;
    if (Status != IP_SUCCESS)
    {
        SetLastError(Status);
        return 0;
    }

    return 1;
}

BOOL
WINAPI
IcmpCloseHandle(
    _In_ HANDLE IcmpHandle)
{
    NTSTATUS Status;

    Status = NtClose(IcmpHandle);
    if (!NT_SUCCESS(Status))
    {
        SetLastError(RtlNtStatusToDosError(Status));
        return FALSE;
    }

    return TRUE;
}

HANDLE
WINAPI
IcmpCreateFile(void)
{
    HANDLE IcmpFile;
    OBJECT_ATTRIBUTES ObjectAttributes;
    IO_STATUS_BLOCK IoStatusBlock;
    UNICODE_STRING DeviceName = RTL_CONSTANT_STRING(L"\\Device\\Ip");
    NTSTATUS Status;

    InitializeObjectAttributes(
        &ObjectAttributes,
        &DeviceName,
        OBJ_CASE_INSENSITIVE,
        NULL,
        NULL);

    Status = NtCreateFile(
        &IcmpFile,
        GENERIC_EXECUTE,
        &ObjectAttributes,
        &IoStatusBlock,
        NULL,
        FILE_ATTRIBUTE_NORMAL,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        FILE_OPEN_IF,
        0,
        NULL,
        0);

    if (!NT_SUCCESS(Status))
    {
        SetLastError(RtlNtStatusToDosError(Status));
        return INVALID_HANDLE_VALUE;
    }

    return IcmpFile;
}

DWORD
WINAPI
IcmpParseReplies(
    _In_ LPVOID ReplyBuffer,
    _In_ DWORD  ReplySize)
{
    PICMP_ECHO_REPLY pEcho;
    DWORD nReplies;

    if (ReplyBuffer == NULL || ReplySize == 0)
        return 0;

    // TODO: Handle ReplyBuffer having more than 1 ICMP_ECHO_REPLY.

    pEcho = (PICMP_ECHO_REPLY)ReplyBuffer;

    if (pEcho->Reserved == 0)
    {
        SetLastError(pEcho->Status);
    }

    nReplies = pEcho->Reserved;
    pEcho->Reserved = 0;

    return nReplies;
}

DWORD
WINAPI
IcmpSendEcho2(
    _In_     HANDLE                 IcmpHandle,
    _In_opt_ HANDLE                 Event,
    _In_opt_ PIO_APC_ROUTINE        ApcRoutine,
    _In_opt_ PVOID                  ApcContext,
    _In_     IPAddr                 DestinationAddress,
    _In_     LPVOID                 RequestData,
    _In_     WORD                   RequestSize,
    _In_opt_ PIP_OPTION_INFORMATION RequestOptions,
    _Out_    LPVOID                 ReplyBuffer,
    _In_     DWORD                  ReplySize,
    _In_     DWORD                  Timeout)
{
    HANDLE hEvent;
    PIO_STATUS_BLOCK IoStatusBlock;
    PVOID InputBuffer;
    PICMP_ECHO_REQUEST Request;
    DWORD nReplies;
    NTSTATUS Status;

    if (ReplySize < sizeof(ICMP_ECHO_REPLY) + sizeof(IO_STATUS_BLOCK))
    {
        SetLastError(ERROR_INSUFFICIENT_BUFFER);
        return 0;
    }

    if (ReplySize < RequestSize + sizeof(ICMP_ECHO_REPLY))
    {
        SetLastError(IP_GENERAL_FAILURE);
        return 0;
    }

    // IO_STATUS_BLOCK will be stored inside ReplyBuffer (in the end)
    // that's because the function may return before device request ends
    IoStatusBlock = (PIO_STATUS_BLOCK)((PUCHAR)ReplyBuffer + ReplySize - sizeof(IO_STATUS_BLOCK));
    ReplySize -= sizeof(IO_STATUS_BLOCK);

    InputBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ReplySize);
    if (InputBuffer == NULL)
    {
        SetLastError(ERROR_NOT_ENOUGH_MEMORY);
        return 0;
    }

    Request = (PICMP_ECHO_REQUEST)InputBuffer;
    Request->Address = DestinationAddress;
    Request->Timeout = Timeout;
    Request->OptionsOffset = sizeof(ICMP_ECHO_REQUEST);
    Request->DataOffset = sizeof(ICMP_ECHO_REQUEST);

    if (RequestOptions != NULL)
    {
        Request->HasOptions = TRUE;
        Request->Ttl = RequestOptions->Ttl;
        Request->Tos = RequestOptions->Tos;
        Request->Flags = RequestOptions->Flags;

        if (RequestOptions->OptionsSize > 0)
        {
            Request->OptionsSize = RequestOptions->OptionsSize;
            Request->DataOffset += Request->OptionsSize;

            CopyMemory(
                (PUCHAR)InputBuffer + sizeof(ICMP_ECHO_REQUEST),
                RequestOptions->OptionsData,
                Request->OptionsSize);
        }
    }

    if (RequestSize > 0)
    {
        Request->DataSize = RequestSize;
        CopyMemory((PUCHAR)InputBuffer + Request->DataOffset, RequestData, RequestSize);
    }

    if (Event == NULL && ApcRoutine == NULL)
    {
        hEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
    }
    else
    {
        hEvent = Event;
    }

    Status = NtDeviceIoControlFile(
        IcmpHandle,
        hEvent,
        ApcRoutine,
        ApcContext,
        IoStatusBlock,
        IOCTL_ICMP_ECHO_REQUEST,
        InputBuffer,
        ReplySize,
        ReplyBuffer,
        ReplySize);         // TODO: Determine how Windows calculates OutputBufferLength.

    // If called asynchronously, return for the caller to handle.
    if (Event != NULL || ApcRoutine != NULL)
    {
        SetLastError(RtlNtStatusToDosError(Status));
        HeapFree(GetProcessHeap(), 0, InputBuffer);
        return 0;
    }

    // Otherwise handle it like IcmpSendEcho.
    if (Status == STATUS_PENDING)
    {
        Status = NtWaitForSingleObject(hEvent, FALSE, NULL);

        if (NT_SUCCESS(Status))
        {
            Status = IoStatusBlock->Status;
        }
    }

    CloseHandle(hEvent);
    HeapFree(GetProcessHeap(), 0, InputBuffer);

    if (!NT_SUCCESS(Status))
    {
        SetLastError(RtlNtStatusToDosError(Status));
        return 0;
    }

    Status = ((PICMP_ECHO_REPLY)ReplyBuffer)->Status;
    if (Status != IP_SUCCESS)
    {
        SetLastError(Status);
    }

    nReplies = ((PICMP_ECHO_REPLY)ReplyBuffer)->Reserved;
    ((PICMP_ECHO_REPLY)ReplyBuffer)->Reserved = 0;

    return nReplies;
}

DWORD
WINAPI
IcmpSendEcho(
    _In_     HANDLE                 IcmpHandle,
    _In_     IPAddr                 DestinationAddress,
    _In_     LPVOID                 RequestData,
    _In_     WORD                   RequestSize,
    _In_opt_ PIP_OPTION_INFORMATION RequestOptions,
    _Out_    LPVOID                 ReplyBuffer,
    _In_     DWORD                  ReplySize,
    _In_     DWORD                  Timeout)
{
    HANDLE hEvent;
    IO_STATUS_BLOCK IoStatusBlock;
    PVOID InputBuffer;
    ULONG InputBufferLength;
    PICMP_ECHO_REQUEST Request;
    DWORD nReplies;
    NTSTATUS Status;

    if (Timeout == 0 || Timeout == (DWORD)-1)
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return 0;
    }

    if (ReplySize < sizeof(ICMP_ECHO_REPLY))
    {
        SetLastError(ERROR_INSUFFICIENT_BUFFER);
        return 0;
    }

    if (ReplySize < RequestSize + sizeof(ICMP_ECHO_REPLY))
    {
        SetLastError(IP_GENERAL_FAILURE);
        return 0;
    }

    InputBufferLength = sizeof(ICMP_ECHO_REQUEST) + RequestSize;
    if (RequestOptions != NULL)
        InputBufferLength += RequestOptions->OptionsSize;

    if (InputBufferLength < ReplySize)
        InputBufferLength = ReplySize;

    InputBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, InputBufferLength);
    if (InputBuffer == NULL)
    {
        SetLastError(ERROR_NOT_ENOUGH_MEMORY);
        return 0;
    }

    Request = (PICMP_ECHO_REQUEST)InputBuffer;
    Request->Address = DestinationAddress;
    Request->Timeout = Timeout;
    Request->OptionsOffset = sizeof(ICMP_ECHO_REQUEST);
    Request->DataOffset = sizeof(ICMP_ECHO_REQUEST);

    if (RequestOptions != NULL)
    {
        Request->HasOptions = TRUE;
        Request->Ttl = RequestOptions->Ttl;
        Request->Tos = RequestOptions->Tos;
        Request->Flags = RequestOptions->Flags;

        if (RequestOptions->OptionsSize > 0)
        {
            Request->OptionsSize = RequestOptions->OptionsSize;
            Request->DataOffset += Request->OptionsSize;

            CopyMemory(
                (PUCHAR)InputBuffer + sizeof(ICMP_ECHO_REQUEST),
                RequestOptions->OptionsData,
                Request->OptionsSize);
        }
    }

    if (RequestSize > 0)
    {
        Request->DataSize = RequestSize;
        CopyMemory((PUCHAR)InputBuffer + Request->DataOffset, RequestData, RequestSize);
    }

    hEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
    if (hEvent == NULL)
    {
        HeapFree(GetProcessHeap(), 0, InputBuffer);
        return 0;
    }

    Status = NtDeviceIoControlFile(
        IcmpHandle,
        hEvent,
        NULL,
        NULL,
        &IoStatusBlock,
        IOCTL_ICMP_ECHO_REQUEST,
        InputBuffer,
        InputBufferLength,
        ReplyBuffer,
        ReplySize);

    if (Status == STATUS_PENDING)
    {
        Status = NtWaitForSingleObject(hEvent, FALSE, NULL);

        if (NT_SUCCESS(Status))
        {
            Status = IoStatusBlock.Status;
        }
    }

    CloseHandle(hEvent);
    HeapFree(GetProcessHeap(), 0, InputBuffer);

    if (!NT_SUCCESS(Status))
    {
        SetLastError(RtlNtStatusToDosError(Status));
        return 0;
    }

    Status = ((PICMP_ECHO_REPLY)ReplyBuffer)->Status;
    if (Status != IP_SUCCESS)
    {
        SetLastError(Status);
    }

    nReplies = ((PICMP_ECHO_REPLY)ReplyBuffer)->Reserved;
    ((PICMP_ECHO_REPLY)ReplyBuffer)->Reserved = 0;

    return nReplies;
}