// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.

#include "precomp.h"

// Exit macros
#define VerExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_VERUTIL, x, s, __VA_ARGS__)
#define VerExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_VERUTIL, x, s, __VA_ARGS__)
#define VerExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_VERUTIL, x, s, __VA_ARGS__)
#define VerExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_VERUTIL, x, s, __VA_ARGS__)
#define VerExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_VERUTIL, x, s, __VA_ARGS__)
#define VerExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_VERUTIL, x, s, __VA_ARGS__)
#define VerExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_VERUTIL, p, x, e, s, __VA_ARGS__)
#define VerExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_VERUTIL, p, x, s, __VA_ARGS__)
#define VerExitOnNullDebugTrace(p, x, e, s, ...)  ExitOnNullDebugTraceSource(DUTIL_SOURCE_VERUTIL, p, x, e, s, __VA_ARGS__)
#define VerExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_VERUTIL, p, x, s, __VA_ARGS__)
#define VerExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_VERUTIL, e, x, s, __VA_ARGS__)

// constants
const DWORD GROW_RELEASE_LABELS = 3;

// Forward declarations.
static int CompareDword(
    __in const DWORD& dw1,
    __in const DWORD& dw2
    );
static HRESULT CompareReleaseLabel(
    __in const VERUTIL_VERSION_RELEASE_LABEL* p1,
    __in LPCWSTR wzVersion1,
    __in const VERUTIL_VERSION_RELEASE_LABEL* p2,
    __in LPCWSTR wzVersion2,
    __out int* pnResult
    );
static HRESULT CompareVersionSubstring(
    __in LPCWSTR wzString1,
    __in int cchCount1,
    __in LPCWSTR wzString2,
    __in int cchCount2,
    __out int* pnResult
    );


DAPI_(HRESULT) VerCompareParsedVersions(
    __in_opt VERUTIL_VERSION* pVersion1,
    __in_opt VERUTIL_VERSION* pVersion2,
    __out int* pnResult
    )
{
    HRESULT hr = S_OK;
    int nResult = 0;
    DWORD cMaxReleaseLabels = 0;
    BOOL fCompareMetadata = FALSE;

    if (pVersion1 && !pVersion1->sczVersion ||
        pVersion2 && !pVersion2->sczVersion)
    {
        ExitFunction1(hr = E_INVALIDARG);
    }

    if (pVersion1 == pVersion2)
    {
        ExitFunction1(nResult = 0);
    }
    else if (pVersion1 && !pVersion2)
    {
        ExitFunction1(nResult = 1);
    }
    else if (!pVersion1 && pVersion2)
    {
        ExitFunction1(nResult = -1);
    }

    nResult = CompareDword(pVersion1->dwMajor, pVersion2->dwMajor);
    if (0 != nResult)
    {
        ExitFunction();
    }

    nResult = CompareDword(pVersion1->dwMinor, pVersion2->dwMinor);
    if (0 != nResult)
    {
        ExitFunction();
    }

    nResult = CompareDword(pVersion1->dwPatch, pVersion2->dwPatch);
    if (0 != nResult)
    {
        ExitFunction();
    }

    nResult = CompareDword(pVersion1->dwRevision, pVersion2->dwRevision);
    if (0 != nResult)
    {
        ExitFunction();
    }

    if (pVersion1->cReleaseLabels)
    {
        if (pVersion2->cReleaseLabels)
        {
            cMaxReleaseLabels = max(pVersion1->cReleaseLabels, pVersion2->cReleaseLabels);
        }
        else
        {
            ExitFunction1(nResult = -1);
        }
    }
    else if (pVersion2->cReleaseLabels)
    {
        ExitFunction1(nResult = 1);
    }

    if (cMaxReleaseLabels)
    {
        for (DWORD i = 0; i < cMaxReleaseLabels; ++i)
        {
            VERUTIL_VERSION_RELEASE_LABEL* pReleaseLabel1 = pVersion1->cReleaseLabels > i ? pVersion1->rgReleaseLabels + i : NULL;
            VERUTIL_VERSION_RELEASE_LABEL* pReleaseLabel2 = pVersion2->cReleaseLabels > i ? pVersion2->rgReleaseLabels + i : NULL;

            hr = CompareReleaseLabel(pReleaseLabel1, pVersion1->sczVersion, pReleaseLabel2, pVersion2->sczVersion, &nResult);
            if (FAILED(hr) || 0 != nResult)
            {
                ExitFunction();
            }
        }
    }

    if (pVersion1->fInvalid)
    {
        if (!pVersion2->fInvalid)
        {
            ExitFunction1(nResult = -1);
        }
        else
        {
            fCompareMetadata = TRUE;
        }
    }
    else if (pVersion2->fInvalid)
    {
        ExitFunction1(nResult = 1);
    }

    if (fCompareMetadata)
    {
        hr = CompareVersionSubstring(pVersion1->sczVersion + pVersion1->cchMetadataOffset, -1, pVersion2->sczVersion + pVersion2->cchMetadataOffset, -1, &nResult);
    }

LExit:
    *pnResult = nResult;
    return hr;
}

DAPI_(HRESULT) VerCompareStringVersions(
    __in_z LPCWSTR wzVersion1,
    __in_z LPCWSTR wzVersion2,
    __in BOOL fStrict,
    __out int* pnResult
    )
{
    HRESULT hr = S_OK;
    VERUTIL_VERSION* pVersion1 = NULL;
    VERUTIL_VERSION* pVersion2 = NULL;
    int nResult = 0;

    hr = VerParseVersion(wzVersion1, 0, fStrict, &pVersion1);
    VerExitOnFailure(hr, "Failed to parse Verutil version '%ls'", wzVersion1);

    hr = VerParseVersion(wzVersion2, 0, fStrict, &pVersion2);
    VerExitOnFailure(hr, "Failed to parse Verutil version '%ls'", wzVersion2);

    hr = VerCompareParsedVersions(pVersion1, pVersion2, &nResult);
    VerExitOnFailure(hr, "Failed to compare parsed Verutil versions '%ls' and '%ls'.", wzVersion1, wzVersion2);

LExit:
    *pnResult = nResult;

    ReleaseVerutilVersion(pVersion1);
    ReleaseVerutilVersion(pVersion2);

    return hr;
}

DAPI_(HRESULT) VerCopyVersion(
    __in VERUTIL_VERSION* pSource,
    __out VERUTIL_VERSION** ppVersion
    )
{
    HRESULT hr = S_OK;
    VERUTIL_VERSION* pCopy = NULL;

    pCopy = reinterpret_cast<VERUTIL_VERSION*>(MemAlloc(sizeof(VERUTIL_VERSION), TRUE));
    VerExitOnNull(pCopy, hr, E_OUTOFMEMORY, "Failed to allocate memory for Verutil version copy.");

    hr = StrAllocString(&pCopy->sczVersion, pSource->sczVersion, 0);
    VerExitOnFailure(hr, "Failed to copy Verutil version string '%ls'.", pSource->sczVersion);

    pCopy->chPrefix = pSource->chPrefix;
    pCopy->dwMajor = pSource->dwMajor;
    pCopy->fHasMajor = pSource->fHasMajor;
    pCopy->dwMinor = pSource->dwMinor;
    pCopy->fHasMinor = pSource->fHasMinor;
    pCopy->dwPatch = pSource->dwPatch;
    pCopy->fHasPatch = pSource->fHasPatch;
    pCopy->dwRevision = pSource->dwRevision;
    pCopy->fHasRevision = pSource->fHasRevision;

    if (pSource->cReleaseLabels)
    {
        hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&pCopy->rgReleaseLabels), 0, sizeof(VERUTIL_VERSION_RELEASE_LABEL), pSource->cReleaseLabels);
        VerExitOnFailure(hr, "Failed to allocate memory for Verutil version release labels copies.");

        pCopy->cReleaseLabels = pSource->cReleaseLabels;

        for (DWORD i = 0; i < pCopy->cReleaseLabels; ++i)
        {
            VERUTIL_VERSION_RELEASE_LABEL* pSourceLabel = pSource->rgReleaseLabels + i;
            VERUTIL_VERSION_RELEASE_LABEL* pCopyLabel = pCopy->rgReleaseLabels + i;

            pCopyLabel->cchLabelOffset = pSourceLabel->cchLabelOffset;
            pCopyLabel->cchLabel = pSourceLabel->cchLabel;
            pCopyLabel->fNumeric = pSourceLabel->fNumeric;
            pCopyLabel->dwValue = pSourceLabel->dwValue;
        }
    }

    pCopy->cchMetadataOffset = pSource->cchMetadataOffset;
    pCopy->fInvalid = pSource->fInvalid;

    *ppVersion = pCopy;
    pCopy = NULL;

LExit:
    ReleaseVerutilVersion(pCopy);

    return hr;
}

DAPI_(void) VerFreeVersion(
    __in VERUTIL_VERSION* pVersion
    )
{
    if (pVersion)
    {
        ReleaseStr(pVersion->sczVersion);
        ReleaseMem(pVersion->rgReleaseLabels);
        ReleaseMem(pVersion);
    }
}

DAPI_(HRESULT) VerParseVersion(
    __in_z LPCWSTR wzVersion,
    __in SIZE_T cchVersion,
    __in BOOL fStrict,
    __out VERUTIL_VERSION** ppVersion
    )
{
    HRESULT hr = S_OK;
    VERUTIL_VERSION* pVersion = NULL;
    LPCWSTR wzString = NULL;
    LPCWSTR wzEnd = NULL;
    LPCWSTR wzPartBegin = NULL;
    LPCWSTR wzPartEnd = NULL;
    BOOL fInvalid = FALSE;
    BOOL fLastPart = FALSE;
    BOOL fTrailingDot = FALSE;
    BOOL fParsedVersionNumber = FALSE;
    BOOL fExpectedReleaseLabels = FALSE;
    DWORD iPart = 0;

    if (!wzVersion || !ppVersion)
    {
        ExitFunction1(hr = E_INVALIDARG);
    }

    // Get string length if not provided.
    if (!cchVersion)
    {
        hr = ::StringCchLengthW(wzVersion, STRSAFE_MAX_CCH, reinterpret_cast<size_t*>(&cchVersion));
        VerExitOnRootFailure(hr, "Failed to get length of version string: %ls", wzVersion);
    }
    else if (INT_MAX < cchVersion)
    {
        VerExitOnRootFailure(hr = E_INVALIDARG, "Version string is too long: %Iu", cchVersion);
    }

    pVersion = reinterpret_cast<VERUTIL_VERSION*>(MemAlloc(sizeof(VERUTIL_VERSION), TRUE));
    VerExitOnNull(pVersion, hr, E_OUTOFMEMORY, "Failed to allocate memory for Verutil version '%ls'.", wzVersion);

    wzString = wzVersion;

    if (L'v' == *wzString || L'V' == *wzString)
    {
        pVersion->chPrefix = *wzString;
        ++wzString;
        --cchVersion;
    }

    wzPartBegin = wzPartEnd = wzString;

    // Save end pointer.
    wzEnd = wzString + cchVersion;

    // Parse version number
    while (wzPartBegin < wzEnd)
    {
        fTrailingDot = FALSE;

        // Find end of part.
        for (;;)
        {
            if (wzPartEnd >= wzEnd)
            {
                fLastPart = TRUE;
                break;
            }

            switch (*wzPartEnd)
            {
            case L'0':
            case L'1':
            case L'2':
            case L'3':
            case L'4':
            case L'5':
            case L'6':
            case L'7':
            case L'8':
            case L'9':
                ++wzPartEnd;
                continue;
            case L'.':
                fTrailingDot = TRUE;
                break;
            case L'-':
            case L'+':
                fLastPart = TRUE;
                break;
            default:
                fInvalid = TRUE;
                break;
            }

            break;
        }

        if (wzPartBegin == wzPartEnd)
        {
            fInvalid = TRUE;
        }

        if (fInvalid)
        {
            break;
        }

        DWORD cchPart = 0;
        hr = ::PtrdiffTToDWord(wzPartEnd - wzPartBegin, &cchPart);
        if (FAILED(hr))
        {
            fInvalid = TRUE;
            break;
        }

        // Parse version part.
        UINT uPart = 0;
        hr = StrStringToUInt32(wzPartBegin, cchPart, &uPart);
        if (FAILED(hr))
        {
            fInvalid = TRUE;
            break;
        }

        switch (iPart)
        {
        case 0:
            pVersion->dwMajor = uPart;
            pVersion->fHasMajor = TRUE;
            break;
        case 1:
            pVersion->dwMinor = uPart;
            pVersion->fHasMinor = TRUE;
            break;
        case 2:
            pVersion->dwPatch = uPart;
            pVersion->fHasPatch = TRUE;
            break;
        case 3:
            pVersion->dwRevision = uPart;
            pVersion->fHasRevision = TRUE;
            break;
        }

        if (fTrailingDot)
        {
            ++wzPartEnd;
        }
        wzPartBegin = wzPartEnd;
        ++iPart;

        if (4 <= iPart || fLastPart)
        {
            fParsedVersionNumber = TRUE;
            break;
        }
    }

    fInvalid |= !fParsedVersionNumber || fTrailingDot;

    if (!fInvalid && wzPartBegin < wzEnd && *wzPartBegin == L'-')
    {
        wzPartBegin = wzPartEnd = wzPartBegin + 1;
        fExpectedReleaseLabels = TRUE;
        fLastPart = FALSE;
    }

    while (fExpectedReleaseLabels && wzPartBegin < wzEnd)
    {
        fTrailingDot = FALSE;

        // Find end of part.
        for (;;)
        {
            if (wzPartEnd >= wzEnd)
            {
                fLastPart = TRUE;
                break;
            }

            if (*wzPartEnd >= L'0' && *wzPartEnd <= L'9' ||
                *wzPartEnd >= L'A' && *wzPartEnd <= L'Z' ||
                *wzPartEnd >= L'a' && *wzPartEnd <= L'z' ||
                *wzPartEnd == L'-')
            {
                ++wzPartEnd;
                continue;
            }
            else if (*wzPartEnd == L'+')
            {
                fLastPart = TRUE;
            }
            else if (*wzPartEnd == L'.')
            {
                fTrailingDot = TRUE;
            }
            else
            {
                fInvalid = TRUE;
            }

            break;
        }

        if (wzPartBegin == wzPartEnd)
        {
            fInvalid = TRUE;
        }

        if (fInvalid)
        {
            break;
        }

        int cchLabel = 0;
        hr = ::PtrdiffTToInt32(wzPartEnd - wzPartBegin, &cchLabel);
        if (FAILED(hr) || 0 > cchLabel)
        {
            fInvalid = TRUE;
            break;
        }

        hr = MemEnsureArraySizeForNewItems(reinterpret_cast<LPVOID*>(&pVersion->rgReleaseLabels), pVersion->cReleaseLabels, 1, sizeof(VERUTIL_VERSION_RELEASE_LABEL), GROW_RELEASE_LABELS);
        VerExitOnFailure(hr, "Failed to allocate memory for Verutil version release labels '%ls'", wzVersion);

        VERUTIL_VERSION_RELEASE_LABEL* pReleaseLabel = pVersion->rgReleaseLabels + pVersion->cReleaseLabels;
        ++pVersion->cReleaseLabels;

        // Try to parse as number.
        UINT uLabel = 0;
        hr = StrStringToUInt32(wzPartBegin, cchLabel, &uLabel);
        if (SUCCEEDED(hr))
        {
            pReleaseLabel->fNumeric = TRUE;
            pReleaseLabel->dwValue = uLabel;
        }

        pReleaseLabel->cchLabelOffset = wzPartBegin - wzString;
        pReleaseLabel->cchLabel = cchLabel;

        if (fTrailingDot)
        {
            ++wzPartEnd;
        }
        wzPartBegin = wzPartEnd;

        if (fLastPart)
        {
            break;
        }
    }

    fInvalid |= fExpectedReleaseLabels && (!pVersion->cReleaseLabels || fTrailingDot);

    if (!fInvalid && wzPartBegin < wzEnd)
    {
        if (*wzPartBegin == L'+')
        {
            wzPartBegin = wzPartEnd = wzPartBegin + 1;
        }
        else
        {
            fInvalid = TRUE;
        }
    }

    if (fInvalid && fStrict)
    {
        ExitFunction1(hr = E_INVALIDARG);
    }

    pVersion->cchMetadataOffset = min(wzPartBegin, wzEnd) - wzString;
    pVersion->fInvalid = fInvalid;

    // If the whole string was invalid, then don't clip off the prefix.
    if (!pVersion->cchMetadataOffset && pVersion->chPrefix)
    {
        pVersion->chPrefix = '\0';
        wzString = wzVersion;
        ++cchVersion;
    }

    hr = StrAllocString(&pVersion->sczVersion, wzString, cchVersion);
    VerExitOnFailure(hr, "Failed to copy Verutil version string '%ls'.", wzVersion);

    *ppVersion = pVersion;
    pVersion = NULL;
    hr = S_OK;

LExit:
    ReleaseVerutilVersion(pVersion);

    return hr;
}

DAPI_(HRESULT) VerVersionFromQword(
    __in DWORD64 qwVersion,
    __out VERUTIL_VERSION** ppVersion
    )
{
    HRESULT hr = S_OK;
    VERUTIL_VERSION* pVersion = NULL;

    pVersion = reinterpret_cast<VERUTIL_VERSION*>(MemAlloc(sizeof(VERUTIL_VERSION), TRUE));
    VerExitOnNull(pVersion, hr, E_OUTOFMEMORY, "Failed to allocate memory for Verutil version from QWORD.");

    pVersion->dwMajor = (WORD)(qwVersion >> 48 & 0xffff);
    pVersion->dwMinor = (WORD)(qwVersion >> 32 & 0xffff);
    pVersion->dwPatch = (WORD)(qwVersion >> 16 & 0xffff);
    pVersion->dwRevision = (WORD)(qwVersion & 0xffff);

    pVersion->fHasMajor = TRUE;
    pVersion->fHasMinor = TRUE;
    pVersion->fHasPatch = TRUE;
    pVersion->fHasRevision = TRUE;

    hr = StrAllocFormatted(&pVersion->sczVersion, L"%lu.%lu.%lu.%lu", pVersion->dwMajor, pVersion->dwMinor, pVersion->dwPatch, pVersion->dwRevision);
    ExitOnFailure(hr, "Failed to allocate and format the version string.");

    pVersion->cchMetadataOffset = lstrlenW(pVersion->sczVersion);

    *ppVersion = pVersion;
    pVersion = NULL;

LExit:
    ReleaseVerutilVersion(pVersion);

    return hr;
}


static int CompareDword(
    __in const DWORD& dw1,
    __in const DWORD& dw2
    )
{
    int nResult = 0;

    if (dw1 > dw2)
    {
        nResult = 1;
    }
    else if (dw1 < dw2)
    {
        nResult = -1;
    }

    return nResult;
}

static HRESULT CompareReleaseLabel(
    __in const VERUTIL_VERSION_RELEASE_LABEL* p1,
    __in LPCWSTR wzVersion1,
    __in const VERUTIL_VERSION_RELEASE_LABEL* p2,
    __in LPCWSTR wzVersion2,
    __out int* pnResult
    )
{
    HRESULT hr = S_OK;
    int nResult = 0;

    if (p1 == p2)
    {
        ExitFunction();
    }
    else if (p1 && !p2)
    {
        ExitFunction1(nResult = 1);
    }
    else if (!p1 && p2)
    {
        ExitFunction1(nResult = -1);
    }

    if (p1->fNumeric)
    {
        if (p2->fNumeric)
        {
            nResult = CompareDword(p1->dwValue, p2->dwValue);
        }
        else
        {
            nResult = -1;
        }
    }
    else
    {
        if (p2->fNumeric)
        {
            nResult = 1;
        }
        else
        {
            hr = CompareVersionSubstring(wzVersion1 + p1->cchLabelOffset, p1->cchLabel, wzVersion2 + p2->cchLabelOffset, p2->cchLabel, &nResult);
        }
    }

LExit:
    *pnResult = nResult;

    return hr;
}

static HRESULT CompareVersionSubstring(
    __in LPCWSTR wzString1,
    __in int cchCount1,
    __in LPCWSTR wzString2,
    __in int cchCount2,
    __out int* pnResult
    )
{
    HRESULT hr = S_OK;
    int nResult = 0;

    nResult = ::CompareStringOrdinal(wzString1, cchCount1, wzString2, cchCount2, TRUE);
    if (!nResult)
    {
        VerExitOnLastError(hr, "Failed to compare version substrings");
    }

LExit:
    *pnResult = nResult - 2;

    return hr;
}