bool Startup()

in ctsTraffic/ctsConfig.cpp [2608:2903]


bool Startup(int argc, _In_reads_(argc) const wchar_t** argv)
{
    ctsConfigInitOnce();

    if (argc < 2)
    {
        PrintUsage();
        return false;
    }

    // ignore the first argv... the exe itself
    const wchar_t** argBegin = argv + 1;
    const wchar_t** argEnd = argv + argc;
    vector args(argBegin, argEnd);

    //
    // first check of they asked for help text
    //
    // ReSharper disable once CppTooWideScopeInitStatement
    const auto foundHelp = ranges::find_if(args,
        [](const wchar_t* arg) -> bool {
            return ctString::ctOrdinalStartsWithCaseInsensative(arg, L"-Help") ||
                   ctString::ctOrdinalEqualsCaseInsensative(arg, L"-?");
        });
    if (foundHelp != end(args))
    {
        const auto* helpString = *foundHelp;
        if (ctString::ctOrdinalEqualsCaseInsensative(helpString, L"-Help:Advanced"))
        {
            PrintUsage(PrintUsageOption::Advanced);
            return false;
        }
        if (ctString::ctOrdinalEqualsCaseInsensative(helpString, L"-Help:Tcp"))
        {
            PrintUsage(PrintUsageOption::Tcp);
            return false;
        }
        if (ctString::ctOrdinalEqualsCaseInsensative(helpString, L"-Help:Udp"))
        {
            PrintUsage(PrintUsageOption::Udp);
            return false;
        }
        if (ctString::ctOrdinalEqualsCaseInsensative(helpString, L"-Help:Logging"))
        {
            PrintUsage(PrintUsageOption::Logging);
            return false;
        }

        PrintUsage();
        return false;
    }

    //
    // create the handle for ctrl-c
    //
    g_configSettings->CtrlCHandle = CreateEventW(nullptr, TRUE, FALSE, nullptr);
    if (!g_configSettings->CtrlCHandle)
    {
        THROW_WIN32_MSG(GetLastError(), "CreateEvent");
    }

    //
    // Many of the below settings must be made in a specified order - comments below help to explain this reasoning
    // note: the IO function definitions must come after *all* other settings
    //       since instantiations of those IO functions might reference global Settings values

    //
    // First:
    // Establish logging settings including verbosity levels and error policies before any functional settings
    // Create the threadpool before instantiating any other object
    //
    ParseForError(args);
    ParseForLogging(args);

    //
    // Next: check for static machine configuration
    // - note these are checking system settings, not user arguments
    //
    CheckSystemSettings();

    //
    // Next: establish the address and port # to be used
    //
    ParseForAddress(args);
    ParseForPort(args);
    ParseForLocalport(args);
    ParseForIfIndex(args);

    //
    // ensure a Port is assigned to all listening addresses and target addresses
    //
    for (auto& addr : g_configSettings->ListenAddresses)
    {
        if (addr.port() == 0x0000)
        {
            addr.SetPort(g_configSettings->Port);
        }
    }
    for (auto& addr : g_configSettings->TargetAddresses)
    {
        if (addr.port() == 0x0000)
        {
            addr.SetPort(g_configSettings->Port);
        }
    }

    if (g_configSettings->OutgoingIfIndex != 0 && !g_configSettings->ListenAddresses.empty())
    {
        throw invalid_argument("-IfIndex can only be used for outgoing connections, not listening sockets");
    }

    //
    // Next: gather the protocol and Pattern to be used
    // - set the threadpool value after identifying the pattern
    ParseForProtocol(args);
    // default to keep-alive on TCP servers
    if (ProtocolType::TCP == g_configSettings->Protocol && !g_configSettings->ListenAddresses.empty())
    {
        g_configSettings->Options |= Keepalive;
    }

    ParseForIoPattern(args);
    ParseForThreadpool(args);
    // validate protocol & pattern combinations
    if (ProtocolType::UDP == g_configSettings->Protocol && IoPatternType::MediaStream != g_configSettings->IoPattern)
    {
        throw invalid_argument("UDP only supports the MediaStream IO Pattern");
    }
    if (ProtocolType::TCP == g_configSettings->Protocol && IoPatternType::MediaStream == g_configSettings->IoPattern)
    {
        throw invalid_argument("TCP does not support the MediaStream IO Pattern");
    }
    // set appropriate defaults for # of connections for TCP vs. UDP
    if (ProtocolType::UDP == g_configSettings->Protocol)
    {
        g_configSettings->ConnectionLimit = c_defaultUdpConnectionLimit;
    }
    else
    {
        g_configSettings->ConnectionLimit = c_defaultTcpConnectionLimit;
    }

    //
    // Next, set the ctsStatusInformation to be used to print status updates for this protocol
    // - this must be called after both set_logging and set_protocol
    //
    if (ProtocolType::TCP == g_configSettings->Protocol)
    {
        g_printStatusInformation = make_shared<ctsTcpStatusInformation>();
    }
    else
    {
        g_printStatusInformation = make_shared<ctsUdpStatusInformation>();
    }

    //
    // Next: capture other various settings which do not have explicit dependencies
    //
    ParseForOptions(args);
    ParseForKeepAlive(args);
    ParseForCompartment(args);
    ParseForConnections(args);
    ParseForThrottleConnections(args);
    ParseForBuffer(args);
    ParseForTransfer(args);
    ParseForIterations(args);
    ParseForServerExitLimit(args);

    ParseForRatelimit(args);
    ParseForTimelimit(args);

    if (g_rateLimitLow > 0LL && g_configSettings->BurstDelay.has_value())
    {
        throw invalid_argument("-RateLimit and -Burstdelay cannot be used concurrently");
    }

    // ReSharper disable once CppTooWideScopeInitStatement
    const auto ratePerPeriod = g_rateLimitLow * g_configSettings->TcpBytesPerSecondPeriod / 1000LL;
    if (g_configSettings->Protocol == ProtocolType::TCP && g_rateLimitLow > 0 && ratePerPeriod < 1)
    {
        throw invalid_argument("RateLimit * RateLimitPeriod / 1000 must be greater than zero - meaning every period should send at least 1 byte");
    }

    //
    // verify jitter logging requirements
    //
    if (g_jitterLogger && g_configSettings->Protocol != ProtocolType::UDP)
    {
        throw invalid_argument("Jitter can only be logged using UDP");
    }
    if (g_jitterLogger && !g_configSettings->ListenAddresses.empty())
    {
        throw invalid_argument("Jitter can only be logged on the client");
    }
    if (g_jitterLogger && g_configSettings->ConnectionLimit != 1)
    {
        throw invalid_argument("Jitter can only be logged for a single UDP connection");
    }

    if (g_mediaStreamSettings.FrameSizeBytes > 0)
    {
        // the buffersize is now effectively the frame size
        g_bufferSizeHigh = 0;
        g_bufferSizeLow = g_mediaStreamSettings.FrameSizeBytes;
        if (g_bufferSizeLow < 20)
        {
            throw invalid_argument("The media stream frame size (buffer) must be at least 20 bytes");
        }
    }

    // validate localport usage
    if (!g_configSettings->ListenAddresses.empty() && g_configSettings->LocalPortLow != 0)
    {
        throw invalid_argument("Cannot specify both -listen and -LocalPort. To listen on a specific port, use -Port:####");
    }
    if (g_configSettings->LocalPortLow != 0)
    {
        // ReSharper disable once CppTooWideScopeInitStatement
        const USHORT numberOfPorts = g_configSettings->LocalPortHigh == 0 ? 1 : static_cast<USHORT>(g_configSettings->LocalPortHigh - g_configSettings->LocalPortLow + 1);
        if (numberOfPorts < g_configSettings->ConnectionLimit)
        {
            throw invalid_argument(
                "Cannot specify more connections than specified local ports. "
                "Reduce the number of connections or increase the range of local ports.");
        }
    }

    //
    // Set the default buffer values as these settings are optional
    //
    g_configSettings->ShouldVerifyBuffers = true;
    g_configSettings->UseSharedBuffer = false;
    ParseForShouldVerifyBuffers(args);
    if (ProtocolType::UDP == g_configSettings->Protocol)
    {
        // UDP clients can never recv into the same shared buffer since it uses it for seq. numbers, etc
        if (!IsListening())
        {
            g_configSettings->UseSharedBuffer = false;
        }
    }

    //
    // finally set the functions to use once all other settings are established
    // set_ioFunction changes global options for socket operation for instance WSA_FLAG_REGISTERED_IO flag
    // - hence it is requirement to invoke it prior to any socket operation
    //
    ParseForIoFunction(args);
    ParseForInlineCompletions(args);
    ParseForMsgWaitAll(args);
    ParseForCreate(args);
    ParseForConnect(args);
    ParseForAccept(args);
    if (!g_configSettings->ListenAddresses.empty())
    {
        // servers 'create' connections when they accept them
        g_configSettings->CreateFunction = g_configSettings->AcceptFunction;
        g_configSettings->ConnectFunction = nullptr;
    }

    g_configSettings->TcpShutdown = TcpShutdownType::GracefulShutdown;
    ParseForShutdown(args);

    ParseForPrepostrecvs(args);
    if (ProtocolType::TCP == g_configSettings->Protocol && g_configSettings->ShouldVerifyBuffers && g_configSettings->PrePostRecvs > 1)
    {
        throw invalid_argument("-PrePostRecvs > 1 requires -Verify:connection when using TCP");
    }
    ParseForPrepostsends(args);
    ParseForRecvbufvalue(args);
    ParseForSendbufvalue(args);

    if (!args.empty())
    {
        wstring errorString;
        for (const auto& argString : args)
        {
            errorString.append(L" ");
            errorString.append(argString);
        }
        errorString.append(L"\n");
        PrintErrorInfoOverride(errorString.c_str());
        throw invalid_argument(ctString::ctConvertToString(errorString).c_str());
    }

    if (ProtocolType::UDP == g_configSettings->Protocol)
    {
        if (const auto timerResult = timeBeginPeriod(1); timerResult != TIMERR_NOERROR)
        {
            THROW_WIN32_MSG(timerResult, "timeBeginPeriod");
        }
        ++g_timePeriodRefCount;
    }

    return true;
}