PowerBasic Winsock-Library

Started by Theo Gottwald, September 04, 2025, 03:55:52 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Theo Gottwald

I have been testing the amazing new GROK-Code and especially QWEN 3 MAX today and it beats Googles Gemini using Powerbasic, and makes impressive results.
In Times of AI an Winsock Library is most important. SO let me share this with you.
The only thing you might need to change to have it compile are the Calls to MCP_Log().
And YES Powerbasic has own TCP-Commands,they go a wide way and then fail.
So this is the Upgrade.

#INCLUDE THIS ONCE
' NW_Library.inc
' ########################################################################
' Advanced TCP Library for PowerBASIC (Definitive Final Version - Part 1)
' - Prefix: NW_ for public API (COMMON)
' - Prefix: INT_ for internal helpers
' - USES OFFICIAL WINSOCK HEADERS
' ########################################################################

#INCLUDE ONCE "Ws2def.inc"
#INCLUDE ONCE "WinSock2.inc"
#INCLUDE ONCE "WS2tcpip.inc"
#INCLUDE ONCE "ws2ipdef.inc"

'
' Functions that are marked as * * * SEALED * * *  MAY NOT BE CHANGED.
' They are tested and correct.
'===============================================================================
' Part 0: Constants & Globals
'===============================================================================

%NW_SOCKET_ERROR    = -1
%NW_WOULDBLOCK      = -2

%NW_MAX_CLIENTS     = 20
%NW_TIMEOUT_DEFAULT = 5000 'ms

%CR=13
%LF=10

GLOBAL g_NW_InitCounter AS LONG
GLOBAL g_NW_Cs AS CRITICAL_SECTION
GLOBAL g_IsShutdown AS LONG
GLOBAL g_MCP_LogLevel AS LONG
GLOBAL g_NW_DebugMode AS LONG
GLOBAL g_NW_HexDumpEnabled AS LONG

GLOBAL g_NW_ActiveThreadCount AS LONG ' Tracks threads actively using the library

GLOBAL g_NW_ShutdownEvent AS DWORD    ' Event handle to signal shutdown completion

THREADED s_Buffer AS STRING  ' Persistent, THREAD-GLOBAL buffer
THREADED s_Socket AS LONG   ' Socket associated with the THREAD-GLOBAL buffer

THREADED g_NW_LastThreadError AS LONG

' Set to 1 if Init was called
GLOBAL g_NW_Initialized AS BYTE

DECLARE SUB MoveMemory LIB "kernel32.dll" ALIAS "RtlMoveMemory" (BYVAL pDst AS DWORD, BYVAL pSrc AS DWORD, BYVAL cb AS LONG)
DECLARE FUNCTION WS_StringToAddressA LIB "Ws2_32.dll" ALIAS "WSAStringToAddressA" (BYVAL AddressString AS DWORD, BYVAL AddressFamily AS LONG, BYVAL lpProtocolInfo AS WSAPROTOCOL_INFOA PTR, BYVAL lpAddress AS DWORD, BYREF lpAddressLength AS LONG) AS LONG
DECLARE SUB WS_freeaddrinfo LIB "Ws2_32.dll" ALIAS "freeaddrinfo" (BYVAL pAddrInfo AS DWORD)
DECLARE FUNCTION WS_ntohs LIB "Ws2_32.dll" ALIAS "ntohs" (BYVAL netshort AS WORD) AS WORD
DECLARE FUNCTION WS_WSAStartup LIB "Ws2_32.dll" ALIAS "WSAStartup" (BYVAL wVersionRequested AS WORD, BYREF lpWSAData AS WSADATA) AS LONG
DECLARE FUNCTION WS_WSACleanup LIB "Ws2_32.dll" ALIAS "WSACleanup" () AS LONG

DECLARE FUNCTION WS_socket LIB "Ws2_32.dll" ALIAS "socket" (BYVAL af AS LONG, BYVAL TPE AS LONG, BYVAL protocol AS LONG) AS DWORD
DECLARE FUNCTION WS_listen LIB "Ws2_32.dll" ALIAS "listen" (BYVAL s AS DWORD, BYVAL backlog AS LONG) AS LONG
DECLARE FUNCTION WS_closesocket LIB "Ws2_32.dll" ALIAS "closesocket" (BYVAL s AS DWORD) AS LONG
DECLARE FUNCTION WS_recvfrom LIB "Ws2_32.dll" ALIAS "recvfrom" (BYVAL s AS DWORD, BYVAL buf AS DWORD, BYVAL LE AS LONG, BYVAL flags AS LONG, BYVAL FR AS DWORD, BYVAL fromlen AS LONG) AS LONG
DECLARE FUNCTION WS_sendto LIB "Ws2_32.dll" ALIAS "sendto" (BYVAL s AS DWORD, BYVAL buf AS DWORD, BYVAL LE AS LONG, BYVAL flags AS LONG, BYVAL to_addr AS DWORD, BYVAL tolen AS LONG) AS LONG
DECLARE FUNCTION WS_WSASelect LIB "Ws2_32.dll" ALIAS "select" (BYVAL nfds AS LONG, BYREF readfds AS fd_setstruc, BYREF writefds AS fd_setstruc, BYREF exceptfds AS fd_setstruc, BYREF TIMEOUT AS timeval) AS LONG
DECLARE FUNCTION WS_Recv LIB "Ws2_32.dll" ALIAS "recv" (BYVAL s AS DWORD, BYVAL buf AS DWORD, BYVAL nLen AS LONG, BYVAL flags AS LONG) AS LONG
DECLARE FUNCTION WS_Send LIB "Ws2_32.dll" ALIAS "send" (BYVAL s AS DWORD, BYVAL buf AS DWORD, BYVAL nLen AS LONG, BYVAL flags AS LONG) AS LONG
DECLARE FUNCTION WS_inet_ntoa LIB "Ws2_32.dll" ALIAS "inet_ntoa" (BYVAL IN AS DWORD) AS DWORD
DECLARE FUNCTION WS_AddrToStringA LIB "Ws2_32.dll" ALIAS "WSAAddressToStringA" (BYVAL lpsaAddress AS DWORD, BYVAL dwAddressLength AS DWORD, BYVAL lpProtocolInfo AS WSAPROTOCOL_INFOA PTR,_
 BYREF lpszAddressString AS ASCIIZ, BYREF lpdwAddressStringLength AS DWORD) AS LONG
DECLARE FUNCTION WS_getnameinfo LIB "Ws2_32.dll" ALIAS "getnameinfo" (BYVAL sa AS DWORD, BYVAL salen AS LONG, BYREF HOS AS ASCIIZ, BYVAL hostlen AS DWORD, BYVAL serv AS DWORD, BYVAL servlen AS DWORD, BYVAL flags AS LONG) AS LONG
DECLARE FUNCTION WS_accept LIB "Ws2_32.dll" ALIAS "accept" (BYVAL s AS DWORD, BYVAL ADDR AS DWORD, BYREF addrlen AS LONG) AS DWORD
DECLARE FUNCTION WS_getpeername LIB "Ws2_32.dll" ALIAS "getpeername" (BYVAL s AS DWORD, BYVAL NAME AS DWORD, BYREF namelen AS LONG) AS LONG
DECLARE FUNCTION WS_getsockname LIB "Ws2_32.dll" ALIAS "getsockname" (BYVAL s AS DWORD, BYVAL NAME AS DWORD, BYREF namelen AS LONG) AS LONG
DECLARE FUNCTION WS_bind LIB "Ws2_32.dll" ALIAS "bind" (BYVAL s AS DWORD, BYVAL ADDR AS DWORD, BYVAL namelen AS LONG) AS LONG ' namelen by value
DECLARE FUNCTION WS_connect LIB "Ws2_32.dll" ALIAS "connect" (BYVAL s AS DWORD, BYVAL NAME AS DWORD, BYVAL namelen AS LONG) AS LONG
DECLARE FUNCTION WS_getaddrinfo LIB "Ws2_32.dll" ALIAS "getaddrinfo" (BYVAL pNodeName AS DWORD, BYVAL pServiceName AS DWORD, BYVAL pHints AS DWORD, BYREF ppResult AS DWORD) AS LONG

DECLARE FUNCTION WSARecv LIB "Ws2_32.dll" ALIAS "WSARecv" (BYVAL s AS DWORD, BYREF lpBuffers AS WSABUF, BYVAL dwBufferCount AS DWORD, BYREF lpNumberOfBytesRecvd AS DWORD, BYREF lpFlags AS DWORD,_
 BYREF lpOverlapped AS WSAOVERLAPPED, BYVAL lpCompletionRoutine AS DWORD) AS LONG

'===============================================================================
' Part 1: Initialization and Cleanup (Thread-Safe)
'===============================================================================
' PURPOSE: Initializes the Winsock library and all necessary resources for
'          multi-threaded use. Should be called once before any other
'          library functions are used.
' UNICODE: Not applicable.
' PARAMS: None.
' RETURNS: %TRUE on success, %FALSE on failure.
FUNCTION NW_Initialize() THREADSAFE COMMON AS LONG
    LOCAL E01 AS WSADATA
    LOCAL N01 AS WORD
    LOCAL N02 AS LONG
    LOCAL E03 AS LONG

    IF g_NW_Initialized = 1 THEN
        N02 = %TRUE
        GOTO enx
    END IF

    MCP_Init()

    ' --- ONE-TIME RESOURCE ALLOCATION ---
    InitializeCriticalSection g_NW_Cs
    InitializeCriticalSection g_mcp_log_queue_cs ' Ensure this is initialized too

    ' Create shutdown event (manual reset, initially non-signaled)
    g_NW_ShutdownEvent = CreateEvent(BYVAL %NULL, %TRUE, %FALSE, BYVAL %NULL)
    IF g_NW_ShutdownEvent = 0 THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_Initialize: Failed to create shutdown event, Error " & STR$(GetLastError()))
        END IF
        DeleteCriticalSection g_NW_Cs
        DeleteCriticalSection g_mcp_log_queue_cs
        N02 = %FALSE
        GOTO enx
    END IF

    IF g_MCP_LogLevel > 1 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_Initialize: Starting Winsock initialization...")
    END IF

    EnterCriticalSection g_NW_Cs
    IF g_NW_InitCounter > 0 THEN
        INCR g_NW_InitCounter
        IF g_MCP_LogLevel > 1 THEN
            MCP_Log(%MCP_LOG_DEBUG, "NW_Initialize: Already initialized, counter incremented to " & STR$(g_NW_InitCounter))
        END IF
        N02 = %TRUE
    ELSE
        N01 = MAK(WORD, 2, 2)
        E03 = WS_WSAStartup(N01, E01)
        IF E03 = 0 THEN
            IF LOBYTE(E01.wVersion) <> 2 OR HIBYTE(E01.wVersion) <> 2 THEN
                WS_WSACleanup()
                IF g_MCP_LogLevel > 0 THEN
                    MCP_Log(%MCP_LOG_ERROR, "NW_Initialize: Version mismatch, got " & STR$(LOBYTE(E01.wVersion)) & "." & STR$(HIBYTE(E01.wVersion)) & ", expected 2.2")
                END IF
                N02 = %FALSE
            ELSE
                g_NW_InitCounter = 1
                g_NW_ActiveThreadCount = 0 ' Initialize thread counter
                IF g_MCP_LogLevel > 1 THEN
                    MCP_Log(%MCP_LOG_DEBUG, "NW_Initialize: Successfully initialized Winsock version " & STR$(LOBYTE(E01.wVersion)) & "." & STR$(HIBYTE(E01.wVersion)))
                END IF
                N02 = %TRUE
            END IF
        ELSE
            IF g_MCP_LogLevel > 0 THEN
                LOCAL errMsg AS STRING
                SELECT CASE E03
                    CASE %WSASYSNOTREADY      : errMsg = "Network subsystem not ready"
                    CASE %WSAVERNOTSUPPORTED  : errMsg = "Requested version not supported"
                    CASE %WSAEINVAL           : errMsg = "Invalid arguments"
                    CASE ELSE                 : errMsg = "WSAStartup failed"
                END SELECT
                MCP_Log(%MCP_LOG_ERROR, "NW_Initialize: " & errMsg & ", WSAError " & STR$(E03))
            END IF
            N02 = %FALSE
        END IF
    END IF
    LeaveCriticalSection g_NW_Cs

    IF N02 = %TRUE THEN
        g_NW_Initialized = 1
        IF g_MCP_LogLevel > 1 THEN
            MCP_Log(%MCP_LOG_DEBUG, "NW_Initialize: Initialization complete.")
        END IF
    ELSE
        ' Cleanup on failure
        IF g_NW_ShutdownEvent THEN
            CloseHandle(g_NW_ShutdownEvent)
            g_NW_ShutdownEvent = 0
        END IF
        DeleteCriticalSection g_NW_Cs
        DeleteCriticalSection g_mcp_log_queue_cs
    END IF

enx:
    FUNCTION = N02
END FUNCTION
'===============================================================================
' PURPOSE: Cleans up and terminates the Winsock library usage. Should be called
'          once for every successful call to NW_Initialize.
' UNICODE: Not applicable.
' PARAMS: None.
' RETURNS: None.
SUB NW_Shutdown() THREADSAFE COMMON
    LOCAL N01 AS LONG
    LOCAL E01 AS LONG
    LOCAL waitResult AS LONG

    EnterCriticalSection g_NW_Cs
    IF g_NW_InitCounter > 0 THEN
        DECR g_NW_InitCounter
        IF g_NW_InitCounter = 0 THEN
            ' Wait for all active threads to finish (up to 30 seconds)
            IF g_NW_ActiveThreadCount > 0 AND g_NW_ShutdownEvent THEN
                IF g_MCP_LogLevel > 1 THEN
                    MCP_Log(%MCP_LOG_INFO, "NW_Shutdown: Waiting for " & STR$(g_NW_ActiveThreadCount) & " active threads to finish...")
                END IF
                LeaveCriticalSection g_NW_Cs ' Release lock before waiting

                waitResult = WaitForSingleObject(g_NW_ShutdownEvent, 30000) ' 30 second timeout
                SELECT CASE waitResult
                    CASE %WAIT_OBJECT_0
                        IF g_MCP_LogLevel > 1 THEN
                            MCP_Log(%MCP_LOG_INFO, "NW_Shutdown: All threads finished gracefully.")
                        END IF
                    CASE %WAIT_TIMEOUT
                        IF g_MCP_LogLevel > 0 THEN
                            MCP_Log(%MCP_LOG_ERROR, "NW_Shutdown: Timeout waiting for threads to finish. Active threads: " & STR$(g_NW_ActiveThreadCount))
                        END IF
                    CASE ELSE
                        E01 = GetLastError()
                        IF g_MCP_LogLevel > 0 THEN
                            MCP_Log(%MCP_LOG_ERROR, "NW_Shutdown: WaitForSingleObject failed, Error " & STR$(E01))
                        END IF
                END SELECT

                EnterCriticalSection g_NW_Cs ' Re-acquire lock for cleanup
            END IF

            ' Perform Winsock cleanup
            N01 = WS_WSACleanup()
            IF N01 = 0 THEN
                IF g_MCP_LogLevel > 1 THEN
                    MCP_Log(%MCP_LOG_DEBUG, "NW_Shutdown: Successfully cleaned up Winsock.")
                END IF
            ELSE
                E01 = INT_GetAndStoreError()
                IF g_MCP_LogLevel > 0 THEN
                    LOCAL errMsg AS STRING
                    SELECT CASE E01
                        CASE %WSANOTINITIALISED
                            errMsg = "Winsock not initialized or already cleaned up"
                        CASE %WSAENETDOWN
                            errMsg = "Network subsystem failed"
                        CASE ELSE
                            errMsg = "WS_WSACleanup failed"
                    END SELECT
                    MCP_Log(%MCP_LOG_ERROR, "NW_Shutdown: " & errMsg & ", WSAError " & STR$(E01))
                END IF
            END IF

            ' Cleanup resources
            IF g_NW_ShutdownEvent THEN
                CloseHandle(g_NW_ShutdownEvent)
                g_NW_ShutdownEvent = 0
            END IF
            LeaveCriticalSection g_NW_Cs
            DeleteCriticalSection g_NW_Cs
            DeleteCriticalSection g_mcp_log_queue_cs
        ELSE
            IF g_MCP_LogLevel > 1 THEN
                MCP_Log(%MCP_LOG_DEBUG, "NW_Shutdown: Counter decremented to " & STR$(g_NW_InitCounter))
            END IF
            LeaveCriticalSection g_NW_Cs
        END IF
    ELSE
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_DEBUG, "NW_Shutdown: Counter already zero, no cleanup performed.")
        END IF
        LeaveCriticalSection g_NW_Cs
    END IF
END SUB
'===============================================================================
' PURPOSE: Gracefully shuts down a socket for send or receive.
' PARAMS: U01 (socket), how (%SD_SEND, %SD_RECEIVE, %SD_BOTH)
' RETURNS: 0 on success, %NW_SOCKET_ERROR on failure.
FUNCTION NW_ShutdownSocket(BYVAL U01 AS DWORD, BYVAL how AS LONG) COMMON AS LONG
    LOCAL E01 AS LONG ' Error variable
    IF shutdown(U01, how) = 0 THEN
        FUNCTION = 0
    ELSE
        E01 = INT_GetAndStoreError()
        FUNCTION = %NW_SOCKET_ERROR
        ' Suppress logging for listening sockets (WSAENOTCONN is common/expected)
        IF E01 <> %WSAENOTCONN AND g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_DEBUG, "NW_ShutdownSocket: Failed on socket " & STR$(U01) & ", WSAError " & STR$(E01))
        END IF
    END IF
END FUNCTION
'===============================================================================
' Part 2: Socket Creation and Connection (High-Level)
'===============================================================================
' PURPOSE: Creates a listening socket bound to a specified IP and port.
' UNICODE: IP address is ASCII/ANSI; no Unicode issues.
' PARAMS: S01 (IP address or "" for any), T01 (port), T02 (backlog).
' RETURNS: Valid socket on success, %INVALID_SOCKET on failure.
' * * * SEALED * * *
FUNCTION NW_Listen(BYVAL S01 AS STRING, BYVAL T01 AS LONG, BYVAL T02 AS LONG) COMMON AS DWORD
    LOCAL E01 AS ADDRINFOA
    LOCAL E02 AS ADDRINFOA PTR
    LOCAL E03 AS ADDRINFOA PTR
    LOCAL E04 AS STRING
    LOCAL E05 AS LONG
    LOCAL E06 AS DWORD
    LOCAL E07 AS LONG
    LOCAL E08 AS LONG
    LOCAL R01 AS DWORD
    LOCAL tempAddrStr AS STRING
    LOCAL tempSockAddr AS SOCKADDR_STORAGE
    LOCAL addrCount AS LONG
    LOCAL nodeName AS ASCIIZ * 256
    LOCAL servName AS ASCIIZ * 32
    ' Log entry
    IF g_MCP_LogLevel > 1 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_Listen: Starting with IP='" & S01 & "', port=" & STR$(T01) & ", backlog=" & STR$(T02) & ", log level=" & STR$(g_MCP_LogLevel))
        X_AU "[DEBUG] NW_Listen: Starting with IP='" & S01 & "', port=" & STR$(T01) & ", backlog=" & STR$(T02) & ", log level=" & STR$(g_MCP_LogLevel)
    END IF
    ' Initialize
    R01 = %INVALID_SOCKET
    ' Prepare service (port)
    servName = FORMAT$(T01)
    IF LEN(servName) = 0 OR T01 < 1 OR T01 > 65535 THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_Listen: Invalid port number " & STR$(T01) & " (must be 1-65535)")
            X_AU "[ERROR] NW_Listen: Invalid port number " & STR$(T01) & " (must be 1-65535)"
        END IF
        GOTO cleanup
    END IF
    IF g_MCP_LogLevel > 2 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_Listen: Service port string: '" & servName & "'")
        X_AU "[DEBUG] NW_Listen: Service port string: '" & servName & "'"
    END IF
    ' Set hints for passive listening
    FILLMEMORY(VARPTR(E01), SIZEOF(E01), 0)
    E01.ai_family = %AF_INET
    E01.ai_socktype = %SOCK_STREAM
    E01.ai_protocol = %IPPROTO_TCP
    E01.ai_flags = %AI_PASSIVE
    IF g_MCP_LogLevel > 2 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_Listen: Hints set: family=" & STR$(E01.ai_family) & ", socktype=" & STR$(E01.ai_socktype) & ", protocol=" & STR$(E01.ai_protocol) & ", flags=" & STR$(E01.ai_flags))
        X_AU "[DEBUG] NW_Listen: Hints set: family=" & STR$(E01.ai_family) & ", socktype=" & STR$(E01.ai_socktype) & ", protocol=" & STR$(E01.ai_protocol) & ", flags=" & STR$(E01.ai_flags)
    END IF
    ' Validate and set IP
    nodeName = IIF$(LEN(S01), S01, "0.0.0.0")
    IF g_MCP_LogLevel > 2 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_Listen: Using IP address: '" & nodeName & "'")
        X_AU "[DEBUG] NW_Listen: Using IP address: '" & nodeName & "'"
    END IF
    ' Get address info
    E05 = WS_getaddrinfo(VARPTR(nodeName), VARPTR(servName), VARPTR(E01), E02)
    IF E05 <> 0 THEN
        IF g_MCP_LogLevel > 0 THEN
            ' This function returns a direct error code, does not use GetLastError
            MCP_Log(%MCP_LOG_ERROR, "NW_Listen: WS_getaddrinfo failed for IP '" & nodeName & "', port " & STR$(T01) & " -> " & NW_GaiErrorText(E05) & " (" & STR$(E05) & ")")
        END IF
        GOTO cleanup
    END IF
    IF g_MCP_LogLevel > 1 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_Listen: getaddrinfo succeeded, processing addresses")
        X_AU "[DEBUG] NW_Listen: getaddrinfo succeeded, processing addresses"
    END IF
    ' Count available addresses for logging
    addrCount = 0
    E03 = E02
    WHILE E03 <> %NULL
        INCR addrCount
        E03 = @E03.ai_next
    WEND
    IF g_MCP_LogLevel > 1 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_Listen: Found " & STR$(addrCount) & " addresses to try")
        X_AU "[DEBUG] NW_Listen: Found " & STR$(addrCount) & " addresses to try"
    END IF
    IF addrCount = 0 THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_Listen: No addresses returned by getaddrinfo for IP '" & nodeName & "', port " & STR$(T01))
            X_AU "[ERROR] NW_Listen: No addresses returned by getaddrinfo for IP '" & nodeName & "', port " & STR$(T01)
        END IF
        GOTO cleanup
    END IF
    ' Try each address
    E03 = E02
    WHILE E03 <> %NULL
        IF g_MCP_LogLevel > 2 THEN
            MEMORY COPY @E03.ai_addr, VARPTR(tempSockAddr), MIN(SIZEOF(SOCKADDR_STORAGE), @E03.ai_addrlen)
            tempSockAddr.ss_family = @E03.ai_family
            IF NW_AddrToString(tempSockAddr, @E03.ai_addrlen, tempAddrStr) THEN
                MCP_Log(%MCP_LOG_DEBUG, "NW_Listen: Trying address family " & STR$(@E03.ai_family) & ", addr=" & tempAddrStr & ", addrlen=" & STR$(@E03.ai_addrlen))
                X_AU "[DEBUG] NW_Listen: Trying address family " & STR$(@E03.ai_family) & ", addr=" & tempAddrStr & ", addrlen=" & STR$(@E03.ai_addrlen)
            ELSE
                MCP_Log(%MCP_LOG_DEBUG, "NW_Listen: Trying address family " & STR$(@E03.ai_family) & ", addr=, addrlen=" & STR$(@E03.ai_addrlen))
                X_AU "[DEBUG] NW_Listen: Trying address family " & STR$(@E03.ai_family) & ", addr=, addrlen=" & STR$(@E03.ai_addrlen)
            END IF
        END IF
        ' Create socket
        E06 = WS_socket(@E03.ai_family, @E03.ai_socktype, @E03.ai_protocol)
        IF E06 = %INVALID_SOCKET THEN
            E05 = INT_GetAndStoreError() ' <--- MODIFIED 1
            IF g_MCP_LogLevel > 0 THEN
                MCP_Log(%MCP_LOG_ERROR, "NW_Listen: WS_socket failed for address family " & STR$(@E03.ai_family) & ", WSAError " & STR$(E05) & " (" & ERROR$(E05) & ")")
                X_AU "[ERROR] NW_Listen: WS_socket failed for address family " & STR$(@E03.ai_family) & ", WSAError " & STR$(E05) & " (" & ERROR$(E05) & ")"
            END IF
        ELSE
            IF g_MCP_LogLevel > 2 THEN
                MCP_Log(%MCP_LOG_DEBUG, "NW_Listen: Socket created: " & STR$(E06))
                X_AU "[DEBUG] NW_Listen: Socket created: " & STR$(E06)
            END IF
            ' Set SO_REUSEADDR
            E08 = %TRUE
            E07 = setsockopt(E06, %SOL_SOCKET, %SO_REUSEADDR, BYVAL VARPTR(E08), SIZEOF(E08))
            IF E07 = %SOCKET_ERROR THEN
                E05 = INT_GetAndStoreError() ' <--- MODIFIED 2
                IF g_MCP_LogLevel > 0 THEN
                    MCP_Log(%MCP_LOG_ERROR, "NW_Listen: setsockopt SO_REUSEADDR failed for socket " & STR$(E06) & ", WSAError " & STR$(E05) & " (" & ERROR$(E05) & ")")
                    X_AU "[ERROR] NW_Listen: setsockopt SO_REUSEADDR failed for socket " & STR$(E06) & ", WSAError " & STR$(E05) & " (" & ERROR$(E05) & ")"
                END IF
                WS_closesocket(E06)
                E06 = %INVALID_SOCKET
            ELSE
                IF g_MCP_LogLevel > 2 THEN
                    MCP_Log(%MCP_LOG_DEBUG, "NW_Listen: SO_REUSEADDR set successfully for socket " & STR$(E06))
                    X_AU "[DEBUG] NW_Listen: SO_REUSEADDR set successfully for socket " & STR$(E06)
                END IF
                ' Bind
                E07 = WS_bind(E06, @E03.ai_addr, @E03.ai_addrlen)
                IF E07 = %SOCKET_ERROR THEN
                    E05 = INT_GetAndStoreError() ' <--- MODIFIED 3
                    IF g_MCP_LogLevel > 0 THEN
                        MCP_Log(%MCP_LOG_ERROR, "NW_Listen: WS_bind failed for addr=" & tempAddrStr & ", socket=" & STR$(E06) & ", WSAError " & STR$(E05) & " (" & ERROR$(E05) & ")")
                        X_AU "[ERROR] NW_Listen: WS_bind failed for addr=" & tempAddrStr & ", socket=" & STR$(E06) & ", WSAError " & STR$(E05) & " (" & ERROR$(E05) & ")"
                    END IF
                    WS_closesocket(E06)
                    E06 = %INVALID_SOCKET
                ELSE
                    IF g_MCP_LogLevel > 2 THEN
                        MCP_Log(%MCP_LOG_DEBUG, "NW_Listen: Bind successful for socket " & STR$(E06))
                        X_AU "[DEBUG] NW_Listen: Bind successful for socket " & STR$(E06)
                    END IF
                    ' Listen
                    E07 = WS_listen(E06, T02)
                    IF E07 = %SOCKET_ERROR THEN
                        E05 = INT_GetAndStoreError() ' <--- MODIFIED 4
                        IF g_MCP_LogLevel > 0 THEN
                            MCP_Log(%MCP_LOG_ERROR, "NW_Listen: WS_listen failed for socket " & STR$(E06) & ", WSAError " & STR$(E05) & " (" & ERROR$(E05) & ")")
                            X_AU "[ERROR] NW_Listen: WS_listen failed for socket " & STR$(E06) & ", WSAError " & STR$(E05) & " (" & ERROR$(E05) & ")"
                        END IF
                        WS_closesocket(E06)
                        E06 = %INVALID_SOCKET
                    ELSE
                        R01 = E06
                        ' Set non-blocking mode for reliable timeouts
                        E07 = NW_SetNonBlocking(R01, %TRUE)
                        IF E07 = %SOCKET_ERROR THEN
                            E05 = INT_GetAndStoreError() ' <--- MODIFIED 5
                            IF g_MCP_LogLevel > 0 THEN
                                MCP_Log(%MCP_LOG_ERROR, "NW_Listen: NW_SetNonBlocking failed for socket " & STR$(R01) & ", WSAError " & STR$(E05) & " (" & ERROR$(E05) & ")")
                                X_AU "[ERROR] NW_Listen: NW_SetNonBlocking failed for socket " & STR$(R01) & ", WSAError " & STR$(E05) & " (" & ERROR$(E05) & ")"
                            END IF
                            WS_closesocket(R01)
                            R01 = %INVALID_SOCKET
                        ELSE
                            IF g_MCP_LogLevel > 1 THEN
                                MCP_Log(%MCP_LOG_DEBUG, "NW_Listen: Listening socket created on port " & STR$(T01) & ", socket=" & STR$(R01))
                                X_AU "[DEBUG] NW_Listen: Listening socket created on port " & STR$(T01) & ", socket=" & STR$(R01)
                            END IF
                            GOTO cleanup
                        END IF
                    END IF
                END IF
            END IF
        END IF
        E03 = @E03.ai_next
    WEND
    IF R01 = %INVALID_SOCKET THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_Listen: Failed to find suitable address to bind and listen for IP '" & nodeName & "', port " & STR$(T01) & ", tried " & STR$(addrCount) & " addresses")
            X_AU "[ERROR] NW_Listen: Failed to find suitable address to bind and listen for IP '" & nodeName & "', port " & STR$(T01) & ", tried " & STR$(addrCount) & " addresses"
        END IF
    END IF
cleanup:
    IF E02 THEN WS_freeaddrinfo(E02)
    IF g_MCP_LogLevel > 1 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_Listen: Cleanup complete, returning socket " & STR$(R01))
        X_AU "[DEBUG] NW_Listen: Cleanup complete, returning socket " & STR$(R01)
    END IF
    FUNCTION = R01
END FUNCTION
' ===============================================================================

' ===============================================================================




' ===============================================================================
' PURPOSE: Connects to a remote host on a specified port with a timeout.
' UNICODE: Host/IP is ASCII/ANSI; no Unicode issues.
' PARAMS: S01 (host or IP), T01 (port), T02 (timeout in seconds)
' RETURNS: Valid connected socket on success, %INVALID_SOCKET on failure.
' * * * SEALED * * *
FUNCTION NW_Connect(BYVAL S01 AS STRING, BYVAL T01 AS LONG, OPT BYVAL T02 AS LONG) COMMON AS DWORD
    LOCAL E01 AS ADDRINFOA
    LOCAL E02 AS ADDRINFOA PTR
    LOCAL E03 AS ADDRINFOA PTR
    LOCAL E05 AS LONG
    LOCAL E06 AS DWORD
    LOCAL E07 AS LONG
    LOCAL R01 AS DWORD
    LOCAL nodeName AS ASCIIZ * 256
    LOCAL servName AS ASCIIZ * 32
    LOCAL writeSet AS fd_setstruc
    LOCAL errorSet AS fd_setstruc
    LOCAL tv AS timeval
    LOCAL soError AS LONG
    LOCAL soErrorLen AS LONG
    LOCAL effectiveTimeout AS LONG
    R01 = %INVALID_SOCKET
    IF T02 <= 0 THEN effectiveTimeout = %NW_TIMEOUT_DEFAULT / 1000 ELSE effectiveTimeout = T02
    IF g_MCP_LogLevel > 1 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_Connect: Starting to " & S01 & ":" & STR$(T01) & ", timeout=" & STR$(effectiveTimeout) & "s")
    END IF
    nodeName = S01
    servName = FORMAT$(T01)
    ' Validate parameters before use
    IF LEN(TRIM$(nodeName)) = 0 OR T01 < 1 OR T01 > 65535 THEN
        IF g_MCP_LogLevel > 0 THEN MCP_Log(%MCP_LOG_ERROR, "NW_Connect: Invalid host or port provided.")
        GOTO cleanup
    END IF
    FILLMEMORY(VARPTR(E01), SIZEOF(E01), 0)
    E01.ai_family = %AF_INET  ' Force IPv4 to match server; avoid AF_UNSPEC issues
    E01.ai_socktype = %SOCK_STREAM
    E05 = WS_getaddrinfo(VARPTR(nodeName), VARPTR(servName), VARPTR(E01), E02)
    IF E05 <> 0 THEN
        IF g_MCP_LogLevel > 0 THEN MCP_Log(%MCP_LOG_ERROR, "NW_Connect: getaddrinfo failed: " & NW_GaiErrorText(E05))
        GOTO cleanup
    END IF
    E03 = E02
    WHILE E03 <> %NULL
        E06 = WS_socket(@E03.ai_family, @E03.ai_socktype, @E03.ai_protocol)
        IF E06 = %INVALID_SOCKET THEN
            E03 = @E03.ai_next
            ITERATE
        END IF
        ' Set to non-blocking to enable connection with a timeout
        IF NW_SetNonBlocking(E06, %TRUE) = %SOCKET_ERROR THEN
            WS_closesocket(E06)
            E03 = @E03.ai_next
            ITERATE
        END IF
        ' Initiate the non-blocking connection
        MCP_Log(%MCP_LOG_DEBUG, "NW_Connect: Calling WS_connect on family " & STR$(@E03.ai_family))
        E07 = WS_connect(E06, @E03.ai_addr, @E03.ai_addrlen)
        MCP_Log(%MCP_LOG_DEBUG, "NW_Connect: WS_connect returned " & STR$(E07) & ", immediate WSAGetLastError " & STR$(WSAGetLastError()))
        IF E07 = 0 THEN
             ' Case 1: Success! Connection established immediately (common for localhost).
             R01 = E06
        ELSE
            ' Case 2: Connection did not succeed immediately. Check the error.
            E05 = INT_GetAndStoreError()
            IF E05 = 0 THEN E05 = %WSAEWOULDBLOCK  ' Workaround for anomalous error 0
            IF E05 = %WSAEWOULDBLOCK THEN
                ' Standard case: Connection is in progress. Wait for it with select().
                FD_ZERO(writeSet) : FD_SET(E06, writeSet)
                FD_ZERO(errorSet) : FD_SET(E06, errorSet)
                tv.tv_sec = effectiveTimeout : tv.tv_usec = 0
                E07 = NW_WSASelect(0, BYVAL %NULL, writeSet, errorSet, tv)
                ' Check select() result:
                ' > 0 means a socket became ready.
                ' FD_ISSET(writeSet) means it's ready for writing (connected).
                ' NOT FD_ISSET(errorSet) means there's no error.
                IF E07 > 0 AND FD_ISSET(E06, writeSet) AND NOT FD_ISSET(E06, errorSet) THEN
                    R01 = E06 ' select() indicates a successful connection.
                ELSE
                    ' select() failed, timed out, or found an error.
                    IF E07 = 0 THEN NW_SetLastError(%WSAETIMEDOUT) ' Set specific error for timeout
                    IF g_MCP_LogLevel > 0 THEN
                        soErrorLen = SIZEOF(soError)
                        getsockopt(E06, %SOL_SOCKET, %SO_ERROR, soError, soErrorLen)
                        MCP_Log(%MCP_LOG_ERROR, "NW_Connect: Connection failed. select()=" & STR$(E07) & ", SO_ERROR=" & STR$(soError))
                    END IF
                    WS_closesocket(E06)
                END IF
            ELSE
                ' Case 3: An immediate connection error occurred.
                IF g_MCP_LogLevel > 0 THEN MCP_Log(%MCP_LOG_ERROR, "NW_Connect: Immediate connect failure, WSAError " & STR$(E05))
                WS_closesocket(E06)
            END IF
        END IF
        ' If a valid socket was obtained, set it back to blocking and exit the loop.
        IF R01 <> %INVALID_SOCKET THEN
            NW_SetNonBlocking(R01, %FALSE)
            IF g_MCP_LogLevel > 1 THEN MCP_Log(%MCP_LOG_DEBUG, "NW_Connect: Connected successfully.")
            GOTO cleanup
        END IF
        ' If we're here, this address failed, try the next one in the list.
        E03 = @E03.ai_next
    WEND
cleanup:
    IF E02 THEN WS_freeaddrinfo(E02)
    IF R01 = %INVALID_SOCKET AND g_MCP_LogLevel > 0 THEN
        MCP_Log(%MCP_LOG_ERROR, "NW_Connect: Failed to connect to any available address.")
    END IF
    FUNCTION = R01
END FUNCTION



' ===============================================================================
' PURPOSE: Closes a socket handle.
' UNICODE: Not applicable; no string operations.
' PARAMS: U01 (DWORD In) - Socket handle to close.
' RETURNS: 0 on success, %NW_SOCKET_ERROR on failure.
'
FUNCTION NW_CloseSocket(BYVAL U01 AS DWORD) THREADSAFE COMMON AS LONG
    LOCAL R01 AS LONG
    LOCAL E01 AS LONG
    R01 = %NW_SOCKET_ERROR ' Assume failure
    IF g_MCP_LogLevel > 1 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_CloseSocket: Closing socket " & STR$(U01))
        X_AU "[DEBUG] NW_CloseSocket: Closing socket " & STR$(U01) ' Add this line
    END IF
    EnterCriticalSection g_NW_Cs
    IF WS_closesocket(U01) = 0 THEN
        R01 = 0
        IF g_MCP_LogLevel > 1 THEN
            MCP_Log(%MCP_LOG_DEBUG, "NW_CloseSocket: Successfully closed socket " & STR$(U01))
            X_AU "[DEBUG] NW_CloseSocket: Successfully closed socket " & STR$(U01) ' Add this line
        END IF
    ELSE
        E01 = INT_GetAndStoreError()
        IF g_MCP_LogLevel > 0 THEN
            LOCAL S01 AS STRING
            SELECT CASE E01
                CASE %WSAENOTSOCK
                    S01 = "Invalid socket"
                CASE %WSAENETDOWN
                    S01 = "Network subsystem failed"
                CASE ELSE
                    S01 = "WS_closesocket failed"
            END SELECT
            MCP_Log(%MCP_LOG_ERROR, "NW_CloseSocket: " & S01 & ", socket=" & STR$(U01) & ", WSAError " & STR$(E01) & " (" & ERROR$(E01) & ")")
            X_AU "[ERROR] NW_CloseSocket: " & S01 & ", socket=" & STR$(U01) & ", WSAError " & STR$(E01) ' Add this line
        END IF
    END IF
    LeaveCriticalSection g_NW_Cs
    FUNCTION = R01
END FUNCTION
' ===============================================================================

' ===============================================================================
' PURPOSE: Accepts an incoming connection and retrieves client info with a timeout.
' UNICODE: Client IP is ASCII/ANSI; no Unicode issues.
' PARAMS: U01 (listening socket), S01 (out: client IP), T01 (out: client port), T02 (timeout in seconds)
' RETURNS: Valid client socket on success, %INVALID_SOCKET on failure/timeout.
' * * * SEALED * * *
FUNCTION NW_Accept(BYVAL U01 AS DWORD, BYREF S01 AS STRING, BYREF T01 AS LONG, OPT BYVAL T02 AS LONG) COMMON AS DWORD
    LOCAL E01 AS SOCKADDR_STORAGE
    LOCAL E02 AS LONG
    LOCAL E03 AS LONG
    LOCAL R01 AS DWORD
    LOCAL readSet AS fd_setstruc
    LOCAL tv AS timeval
    LOCAL effectiveTimeout AS LONG

    ' Initialize
    R01 = %INVALID_SOCKET
    S01 = ""
    T01 = 0
    IF T02 <= 0 THEN effectiveTimeout = %NW_TIMEOUT_DEFAULT / 1000 ELSE effectiveTimeout = T02

    IF g_MCP_LogLevel > 1 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_Accept: Starting accept on socket " & STR$(U01) & ", timeout=" & STR$(effectiveTimeout) & "s")
    END IF

    ' Use select() to wait for a connection to be ready
    FD_ZERO(readSet)
    FD_SET(U01, readSet)
    tv.tv_sec = effectiveTimeout
    tv.tv_usec = 0

    E03 = NW_WSASelect(0, readSet, BYVAL %NULL, BYVAL %NULL, tv)

    IF E03 > 0 THEN
        ' The listening socket is readable, meaning a connection is pending.
        E02 = SIZEOF(E01)
        R01 = WS_accept(U01, VARPTR(E01), E02)

        IF R01 = %INVALID_SOCKET THEN
            E03 = INT_GetAndStoreError()
            IF g_MCP_LogLevel > 0 THEN MCP_Log(%MCP_LOG_ERROR, "NW_Accept: WS_accept failed after select indicated readiness, WSAError " & STR$(E03))
        ELSE
            ' Success: Set accepted socket to non-blocking and get client info
            IF NW_SetNonBlocking(R01, %TRUE) = %SOCKET_ERROR THEN
                E03 = INT_GetAndStoreError()
                IF g_MCP_LogLevel > 0 THEN MCP_Log(%MCP_LOG_ERROR, "NW_Accept: NW_SetNonBlocking failed for accepted socket " & STR$(R01) & ", WSAError " & STR$(E03))
                WS_closesocket(R01)
                R01 = %INVALID_SOCKET
            ELSEIF NW_GetClientInfo(R01, S01, T01, effectiveTimeout) = %FALSE THEN
                IF g_MCP_LogLevel > 0 THEN MCP_Log(%MCP_LOG_ERROR, "NW_Accept: Failed to get client info for accepted socket " & STR$(R01))
                WS_closesocket(R01)
                R01 = %INVALID_SOCKET
            ELSE
                IF g_MCP_LogLevel > 1 THEN MCP_Log(%MCP_LOG_DEBUG, "NW_Accept: Accepted connection from " & S01 & ":" & STR$(T01))
            END IF
        END IF
    ELSEIF E03 = 0 THEN
        ' Timeout
        IF g_MCP_LogLevel > 1 THEN MCP_Log(%MCP_LOG_DEBUG, "NW_Accept: Timeout waiting for connection on socket " & STR$(U01))
        ' No error, just a timeout. R01 remains %INVALID_SOCKET
    ELSE ' E03 < 0
        ' select() error
        E03 = INT_GetAndStoreError()
        IF g_MCP_LogLevel > 0 THEN MCP_Log(%MCP_LOG_ERROR, "NW_Accept: select() failed, WSAError " & STR$(E03))
        ' R01 remains %INVALID_SOCKET
    END IF

    FUNCTION = R01
END FUNCTION


'===============================================================================
' Part 3: Data Transfer
'===============================================================================
' PURPOSE: Initiates a single non-blocking (asynchronous) receive operation.
' UNICODE: Byte-stream safe. Receives raw bytes.
' PARAMS: U01 (socket handle), S01 (STRING Out - MUST BE PRE-ALLOCATED), N01 (length to read), T01 (timeout - IGNORED).
' RETURNS:
'   > 0 : Bytes received.
'   0   : Graceful disconnect (peer closed connection).
'   -1 (%NW_SOCKET_ERROR): A fatal socket error occurred.
'   -2 (%NW_WOULDBLOCK) : Socket buffer is empty (call would block).
' NOTE:
'   This is now a true non-blocking helper. It attempts ONE read and returns the status.
'   The CALLER (NW_RecvStream/NW_LineInputW) is responsible for looping, timeouts, and handling %NW_WOULDBLOCK.
'
FUNCTION NW_RecvAsync(BYVAL U01 AS DWORD, BYREF S01 AS STRING, BYVAL N01 AS LONG, BYVAL T01 AS LONG) COMMON AS LONG
    LOCAL E01 AS WSABUF
    LOCAL N02 AS DWORD ' BytesRecvd (from WSARecv output)
    LOCAL N06 AS DWORD ' Flags (from WSARecv output)
    LOCAL E03 AS LONG ' WSA Error Code
    LOCAL R01 AS LONG ' Return value
    R01 = %NW_SOCKET_ERROR ' Default to fatal error
    N06 = 0
    N02 = 0
    ' Prepare the WSABUF. The caller MUST ensure S01 is pre-allocated (e.g., SPACE$)
    ' This function assumes N01 (length) matches the allocated size of S01.
    E01.dLen = N01
    E01.buf = STRPTR(S01)
    ' Attempt one non-blocking read
    IF WSARecv(U01, E01, 1, N02, N06, BYVAL %NULL, BYVAL %NULL) = 0 THEN
        ' WSARecv call succeeded
        IF N02 = 0 THEN
            ' Graceful disconnect (peer closed the socket)
            IF g_MCP_LogLevel > 1 THEN
                 MCP_Log(%MCP_LOG_DEBUG, "NW_RecvAsync: Graceful disconnect (0 bytes) on socket " & STR$(U01))
            END IF
            R01 = 0 ' Return 0 for disconnect
        ELSE
            ' Data received
            R01 = N02 ' Return bytes read
        END IF
    ELSE
        ' WSARecv call failed
        E03 = INT_GetAndStoreError() ' <--- MODIFIED
        IF E03 = 0 OR E03 = %WSAEWOULDBLOCK THEN
            IF E03 = 0 THEN MCP_Log(%MCP_LOG_DEBUG, "NW_RecvAsync: Treating WSAError 0 as WOULDBLOCK on socket " & STR$(U01))
            R01 = %NW_WOULDBLOCK ' (-2) Buffer is empty, try again later
        ELSE
            ' This is a fatal error
            IF g_MCP_LogLevel > 0 THEN
                LOCAL errMsg AS STRING
                SELECT CASE E03
                    CASE %WSAENOTCONN
                        errMsg = "Socket not connected"
                    CASE %WSAECONNRESET
                        errMsg = "Connection reset by peer"
                    CASE ELSE
                        errMsg = "WSARecv failed, WSAError " & STR$(E03)
                END SELECT
                MCP_Log(%MCP_LOG_ERROR, "NW_RecvAsync: " & errMsg & " on socket " & STR$(U01))
            END IF
            R01 = %NW_SOCKET_ERROR ' (-1) Fatal error
        END IF
    END IF
    FUNCTION = R01
END FUNCTION
'===============================================================================

' ===============================================================================
' PURPOSE: Receives a buffer of data up to a maximum length.
' UNICODE: Byte-stream safe.
' PARAMS: U01 (socket), S01 (out: data), N01 (max length), T01 (timeout - IGNORED).
' RETURNS: >0 (bytes received), 0 (disconnect), -2 (would block), -1 (error).
FUNCTION NW_Recv(BYVAL U01 AS DWORD, BYREF S01 AS STRING, BYVAL N01 AS LONG, BYVAL T01 AS LONG) COMMON AS LONG
    LOCAL N02 AS LONG ' Bytes received
    LOCAL N03 AS LONG ' Max buffer size
    LOCAL E01 AS LONG ' Error code
    LOCAL R01 AS LONG ' Return value
    R01 = %NW_SOCKET_ERROR
    S01 = ""
    N03 = 65536
    IF N01 <= 0 OR N01 > N03 THEN
        IF g_MCP_LogLevel > 0 THEN MCP_Log(%MCP_LOG_ERROR, "NW_Recv: Invalid maxLen " & STR$(N01) & " on socket " & STR$(U01))
        FUNCTION = %NW_SOCKET_ERROR
        EXIT FUNCTION
    END IF
    S01 = SPACE$(N01)
    N02 = WS_Recv(U01, BYVAL STRPTR(S01), N01, 0)
    IF N02 > 0 THEN
        S01 = LEFT$(S01, N02)
        R01 = N02
    ELSEIF N02 = 0 THEN
        S01 = ""
        R01 = 0 ' Graceful close
    ELSE ' N02 is SOCKET_ERROR
        E01 = INT_GetAndStoreError()
        S01 = ""
        IF E01 = 0 THEN MCP_Log(%MCP_LOG_DEBUG, "NW_Recv: Treating WSAError 0 as WOULDBLOCK on socket " & STR$(U01))
        IF E01 = 0 OR E01 = %WSAEWOULDBLOCK THEN
            R01 = %NW_WOULDBLOCK
        ELSE
            IF g_MCP_LogLevel > 0 THEN MCP_Log(%MCP_LOG_ERROR, "NW_Recv: Error on socket " & STR$(U01) & ", WSAError " & STR$(E01))
            R01 = %NW_SOCKET_ERROR
        END IF
    END IF
    FUNCTION = R01
END FUNCTION

'===============================================================================
FUNCTION NW_RecvStream(BYVAL U01 AS LONG, BYREF S01 AS STRING, BYVAL N01 AS LONG, BYVAL T01 AS LONG) COMMON AS LONG
    LOCAL L01 AS STRING
    LOCAL L02 AS STRING
    LOCAL N02 AS LONG ' Result from NW_RecvAsync
    LOCAL N04 AS LONG ' Total bytes received counter
    LOCAL D01 AS DOUBLE ' Master timeout marker
    LOCAL N05 AS LONG ' Chunk read size
    LOCAL N06 AS LONG ' File handle
    LOCAL R01 AS LONG ' Function result
    LOCAL bufferSize AS LONG
    R01 = %NW_SOCKET_ERROR
    N04 = 0 ' Total bytes received
    N05 = 65536 ' 64KB chunks
    IF T01 <= 0 THEN T01 = %NW_TIMEOUT_DEFAULT / 1000
    D01 = TIMER + T01 ' Absolute master timeout
    IF g_MCP_LogLevel > 1 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_RecvStream: Starting receive on socket " & STR$(U01) & ", target=" & STR$(N01) & ", timeout=" & STR$(T01) & "s")
    END IF
    ' Increase socket receive buffer
    bufferSize = 1048576 ' 1MB
    setsockopt(U01, %SOL_SOCKET, %SO_RCVBUF, BYVAL VARPTR(bufferSize), SIZEOF(bufferSize))
    ' Check if S01 is a filename (File Mode) or empty (String Mode)
    IF LEN(S01) THEN
        N06 = FREEFILE
        OPEN S01 FOR BINARY AS N06
        IF ERR THEN
            IF g_MCP_LogLevel > 0 THEN
                MCP_Log(%MCP_LOG_ERROR, "NW_RecvStream: Failed to open file " & S01 & ", Error " & STR$(ERR))
            END IF
            GOTO enx
        END IF
    END IF
    DO
        ' 1. Check Master Timeout
        IF TIMER > D01 THEN
            IF g_MCP_LogLevel > 0 THEN
                MCP_Log(%MCP_LOG_ERROR, "NW_RecvStream: Master timeout after receiving " & STR$(N04) & " bytes on socket " & STR$(U01))
            END IF
            GOTO enx
        END IF
        ' 2. Check if target length (N01) is met (if N01 > 0)
        IF N01 > 0 AND N04 >= N01 THEN
            R01 = N04
            IF g_MCP_LogLevel > 1 THEN
                MCP_Log(%MCP_LOG_DEBUG, "NW_RecvStream: Received target " & STR$(N04) & " bytes on socket " & STR$(U01))
            END IF
            GOTO enx
        END IF
        ' 3. Prepare buffer and call the non-blocking read helper
        L01 = SPACE$(N05)
        N02 = NW_RecvAsync(U01, L01, MIN(N05, IIF(N01 > 0, N01 - N04, N05)), T01)
        ' 4. Handle results from NW_RecvAsync
        SELECT CASE N02
            CASE IS > 0
                ' DATA: Process received data
                L02 = LEFT$(L01, N02)
                IF N06 <> 0 THEN ' File Mode
                    PUT N06, , L02
                ELSE ' String Mode
                    S01 = S01 & L02
                END IF
                N04 = N04 + N02
                IF g_MCP_LogLevel > 2 THEN
                    MCP_Log(%MCP_LOG_DEBUG, "NW_RecvStream: Received " & STR$(N02) & " bytes, total " & STR$(N04) & " on socket " & STR$(U01))
                END IF
            CASE 0
                ' DISCONNECT (Graceful): Treat as EOF
                IF g_MCP_LogLevel > 1 THEN
                    MCP_Log(%MCP_LOG_DEBUG, "NW_RecvStream: Peer closed connection (R:0) after " & STR$(N04) & " bytes on socket " & STR$(U01))
                END IF
                R01 = N04 ' Return bytes received before disconnect
                GOTO enx
            CASE %NW_WOULDBLOCK ' (-2)
                ' NO DATA: Buffer is empty. Yield CPU and re-check timeout.
                SLEEP 10 ' Increased for large transfers
                ITERATE LOOP
            CASE %NW_SOCKET_ERROR ' (-1)
                ' FATAL ERROR (Already logged by NW_RecvAsync)
                IF g_MCP_LogLevel > 0 THEN
                    MCP_Log(%MCP_LOG_ERROR, "NW_RecvStream: NW_RecvAsync failed (-1), halting stream after " & STR$(N04) & " bytes on socket " & STR$(U01))
                END IF
                GOTO enx
        END SELECT
    LOOP
enx:
    IF N06 THEN CLOSE N06
    IF R01 = %NW_SOCKET_ERROR AND N04 > 0 THEN R01 = N04
    FUNCTION = R01
END FUNCTION

FUNCTION INT_ConsumeChunked(BYVAL sock AS DWORD) COMMON AS LONG
    LOCAL total AS LONG, chunkSize AS LONG, S01 AS STRING, S02 AS STRING
    DO
        IF NW_LineInput(sock, S01, 5) = 0 THEN EXIT FUNCTION ' Timeout/disconnect
        chunkSize = VAL("&H" & S01)
        IF chunkSize = 0 THEN EXIT DO
        S02 = SPACE$(chunkSize)
        IF NW_RecvStream(sock, S02, chunkSize, 5) <> chunkSize THEN EXIT FUNCTION
        total += chunkSize
        NW_LineInput(sock, S01, 5) ' Consume trailing CRLF
    LOOP
    FUNCTION = total
END FUNCTION
'===============================================================================
' Part 0: Aliases, constants, globals
'===============================================================================
' ########################################################################
' FUNCTION: NW_Send (non-blocking safe, short vars, single-exit)
'-
' Purpose:
' Sends the entire STRING buffer over a socket, retrying on
' %WSAEWOULDBLOCK until the overall timeout expires.
'
' Parameters:
' U01 (BYVAL DWORD) - Socket handle.
' S01 (BYREF STRING) - Data to send (raw bytes; UTF-8/ASCII/etc.).
' T01 (BYVAL LONG) - Timeout in seconds (<=0 uses %NW_TIMEOUT_DEFAULT).
'
' Returns:
' >= 0 : Total bytes successfully sent.
' -1 : %NW_SOCKET_ERROR on failure or timeout.
'
' Notes:
' - Uses short variable names and avoids reserved identifiers like POS.
' - Single-exit pattern with label 'enx:' and result variable R01.
' - Assumes WS_Send() is declared as in your library.
' ########################################################################
' PURPOSE: Sends an entire STRING buffer over a socket, handling non-blocking
' sockets and timeouts correctly.
' UNICODE: Byte-stream safe.
' PARAMS: U01 (socket), S01 (data to send), T01 (timeout in seconds).
' RETURNS: Total bytes sent, or %NW_SOCKET_ERROR on failure/timeout.
FUNCTION NW_Send(BYVAL U01 AS DWORD, BYREF S01 AS STRING, BYVAL T01 AS LONG) COMMON AS LONG
    LOCAL totalBytesToSend AS LONG
    LOCAL bytesSent AS LONG
    LOCAL currentBytesSent AS LONG
    LOCAL bytesRemaining AS LONG
    LOCAL timeoutMarker AS DOUBLE
    LOCAL E01 AS fd_setstruc ' Write file descriptor set
    LOCAL E02 AS timeval ' Timeout structure for select()
    LOCAL E03 AS LONG ' WSA Error Code (from WSAGetLastError)
    LOCAL E04 AS LONG ' Result of select() call
    LOCAL E05 AS LONG ' Flag for loop exit
    LOCAL E06 AS LONG ' Temporary flag
    LOCAL R01 AS LONG ' Return value (bytes sent or -1)

    R01 = %NW_SOCKET_ERROR ' Assume failure
    totalBytesToSend = LEN(S01)
    bytesSent = 0
    bytesRemaining = totalBytesToSend

    ' Validate input
    IF totalBytesToSend = 0 THEN
        R01 = 0 ' Successfully sent 0 bytes
        GOTO enx
    END IF

    ' Set timeout
    IF T01 <= 0 THEN T01 = %NW_TIMEOUT_DEFAULT / 1000
    timeoutMarker = TIMER + T01

    ' --- START Logging ---
    IF g_MCP_LogLevel > 1 THEN
        MCP_Log %MCP_LOG_DEBUG, "NW_Send: Start sending " & STR$(totalBytesToSend) & " bytes on socket " & STR$(U01) & ", timeout=" & STR$(T01) & "s, marker=" & FORMAT$(timeoutMarker, "#0.000")
        X_AU "[DEBUG] NW_Send: Start sending " & STR$(totalBytesToSend) & " bytes on socket " & STR$(U01) & ", timeout=" & STR$(T01) & "s"
    END IF
    ' --- END Logging ---

    ' Main send loop
    DO
        ' Check overall timeout first
        IF TIMER > timeoutMarker THEN
            ' --- START Logging ---
            IF g_MCP_LogLevel > 0 THEN
                MCP_Log %MCP_LOG_ERROR, "NW_Send: Overall timeout expired while sending on socket " & STR$(U01) & ". Sent " & STR$(bytesSent) & "/" & STR$(totalBytesToSend) & " bytes."
                X_AU "[ERROR] NW_Send: Overall timeout expired on socket " & STR$(U01) & ". Sent " & STR$(bytesSent) & "/" & STR$(totalBytesToSend) & " bytes."
            END IF
            ' --- END Logging ---
            R01 = %NW_SOCKET_ERROR ' Timeout is an error
            GOTO enx
        END IF

        ' Prepare fd_set for select (check if socket is writable)
        FD_ZERO E01
        FD_SET U01, E01

        ' Calculate remaining time for select() timeout
        E02.tv_sec = MIN&(5, INT(timeoutMarker - TIMER)) ' Cap at 5s per select call, prevent negative
        E02.tv_usec = 0
        IF E02.tv_sec < 0 THEN E02.tv_sec = 0 ' Safety check

        ' --- START Logging ---
        IF g_MCP_LogLevel > 2 THEN ' Verbose debug
             MCP_Log %MCP_LOG_DEBUG, "NW_Send: Calling select() for socket " & STR$(U01) & ", bytesSent=" & STR$(bytesSent) & ", select_timeout=" & STR$(E02.tv_sec) & "s"
             X_AU "[DEBUG] NW_Send: select() for socket " & STR$(U01) & ", sent=" & STR$(bytesSent) & ", sel_to=" & STR$(E02.tv_sec) & "s"
        END IF
        ' --- END Logging ---

        ' Wait for socket to become writable (or timeout/error)
        E04 = WS_WSASelect(0, BYVAL %NULL, E01, BYVAL %NULL, E02)

        ' --- START Logging ---
        IF g_MCP_LogLevel > 2 THEN ' Verbose debug
             MCP_Log %MCP_LOG_DEBUG, "NW_Send: select() returned " & STR$(E04) & " for socket " & STR$(U01)
             X_AU "[DEBUG] NW_Send: select() returned " & STR$(E04) & " for socket " & STR$(U01)
        END IF
        ' --- END Logging ---

        ' Check result of select()
        SELECT CASE E04
            CASE IS > 0
                ' Socket is ready for writing
                ' --- START Logging ---
                IF g_MCP_LogLevel > 2 THEN ' Verbose debug
                     MCP_Log %MCP_LOG_DEBUG, "NW_Send: select() indicates socket " & STR$(U01) & " is writable."
                     X_AU "[DEBUG] NW_Send: Socket " & STR$(U01) & " is writable."
                END IF
                ' --- END Logging ---

            CASE 0
                ' select() timed out
                ' --- START Logging ---
                IF g_MCP_LogLevel > 1 THEN
                    MCP_Log %MCP_LOG_WARN, "NW_Send: select() timed out while waiting to write on socket " & STR$(U01) & ". Sent " & STR$(bytesSent) & "/" & STR$(totalBytesToSend) & " bytes so far."
                    X_AU "[WARN] NW_Send: select() timed out for socket " & STR$(U01) & ". Sent " & STR$(bytesSent) & "/" & STR$(totalBytesToSend) & " bytes."
                END IF
                ' --- END Logging ---
                 ' Continue loop to check overall timeout or retry
                 ITERATE DO

            CASE ELSE ' -1, indicating error in select()
                E03 = WSAGetLastError()
                ' --- START Logging ---
                IF g_MCP_LogLevel > 0 THEN
                    MCP_Log %MCP_LOG_ERROR, "NW_Send: select() failed on socket " & STR$(U01) & ", WSAError " & STR$(E03) & ". Sent " & STR$(bytesSent) & "/" & STR$(totalBytesToSend) & " bytes."
                    X_AU "[ERROR] NW_Send: select() failed on socket " & STR$(U01) & ", WSAError " & STR$(E03) & ". Sent " & STR$(bytesSent) & "/" & STR$(totalBytesToSend) & " bytes."
                END IF
                ' --- END Logging ---
                R01 = %NW_SOCKET_ERROR
                GOTO enx
        END SELECT

        ' Socket is ready, attempt to send remaining data
        currentBytesSent = WS_Send(U01, STRPTR(S01) + bytesSent, bytesRemaining, 0)

        ' Check result of send
        IF currentBytesSent = %NW_SOCKET_ERROR THEN
            E03 = WSAGetLastError()

            ' --- START Logging ---
            IF g_MCP_LogLevel > 2 THEN ' Verbose debug
                 MCP_Log %MCP_LOG_DEBUG, "NW_Send: WS_Send returned %NW_SOCKET_ERROR, WSAError=" & STR$(E03) & " on socket " & STR$(U01)
                 X_AU "[DEBUG] NW_Send: WS_Send error " & STR$(E03) & " on socket " & STR$(U01)
            END IF
            ' --- END Logging ---

            ' Handle specific errors
            SELECT CASE E03
                CASE %WSAEWOULDBLOCK, 0 ' Treat 0 as WOULDBLOCK (Unusual, but as per original logic)
                    ' --- START Logging ---
                    IF g_MCP_LogLevel > 2 THEN ' Verbose debug
                         MCP_Log %MCP_LOG_DEBUG, "NW_Send: WS_Send would block (WSAEWOULDBLOCK or 0) on socket " & STR$(U01) & ". Yielding CPU."
                         X_AU "[DEBUG] NW_Send: Would block on socket " & STR$(U01) & ". Yielding."
                    END IF
                    ' --- END Logging ---
                    SLEEP 10 ' Brief pause before retrying select/send
                    ITERATE DO ' Go back to select loop

                CASE ELSE
                    ' Fatal error during send
                    ' --- START Logging ---
                    IF g_MCP_LogLevel > 0 THEN
                        MCP_Log %MCP_LOG_ERROR, "NW_Send: WS_Send failed fatally on socket " & STR$(U01) & ", WSAError " & STR$(E03) & " after sending " & STR$(bytesSent) & " bytes."
                         X_AU "[ERROR] NW_Send: WS_Send fatal error " & STR$(E03) & " on socket " & STR$(U01) & " after " & STR$(bytesSent) & " bytes."
                    END IF
                    ' --- END Logging ---
                    R01 = %NW_SOCKET_ERROR
                    GOTO enx
            END SELECT
        ELSE
            ' Send was successful, update counters
            bytesSent = bytesSent + currentBytesSent
            bytesRemaining = bytesRemaining - currentBytesSent

            ' --- START Logging ---
            IF g_MCP_LogLevel > 2 THEN ' Verbose debug
                 MCP_Log %MCP_LOG_DEBUG, "NW_Send: Successfully sent " & STR$(currentBytesSent) & " bytes on socket " & STR$(U01) & ". Total sent now " & STR$(bytesSent) & "/" & STR$(totalBytesToSend) & "."
                 X_AU "[DEBUG] NW_Send: Sent " & STR$(currentBytesSent) & " bytes (total " & STR$(bytesSent) & ") on socket " & STR$(U01)
            END IF
            ' --- END Logging ---

            ' Check if all data has been sent
            IF bytesRemaining <= 0 THEN
                R01 = bytesSent ' Success, return total bytes sent
                ' --- START Logging ---
                IF g_MCP_LogLevel > 1 THEN
                    MCP_Log %MCP_LOG_INFO, "NW_Send: Successfully sent all " & STR$(bytesSent) & " bytes on socket " & STR$(U01) & "."
                    X_AU "[INFO] NW_Send: Completed sending " & STR$(bytesSent) & " bytes on socket " & STR$(U01)
                END IF
                ' --- END Logging ---
                GOTO enx
            END IF
        END IF

    LOOP WHILE bytesRemaining > 0 AND TIMER <= timeoutMarker

    ' If loop exits without sending all data, it's a timeout
    IF bytesRemaining > 0 THEN
         ' --- START Logging ---
         IF g_MCP_LogLevel > 0 THEN
             MCP_Log %MCP_LOG_ERROR, "NW_Send: Loop exit - Timeout sending on socket " & STR$(U01) & ". Sent " & STR$(bytesSent) & "/" & STR$(totalBytesToSend) & " bytes."
             X_AU "[ERROR] NW_Send: Loop exit - Timeout on socket " & STR$(U01) & ". Sent " & STR$(bytesSent) & "/" & STR$(totalBytesToSend) & " bytes."
         END IF
         ' --- END Logging ---
         R01 = %NW_SOCKET_ERROR
    END IF

enx:
    FUNCTION = R01 ' Return the final result
END FUNCTION
'===============================================================================




'===============================================================================
'
'===============================================================================
' PURPOSE: Sends a complete and properly formatted HTTP response, with optional chunked encoding for dynamic streaming.
' UNICODE: Byte-stream safe. statusText and headers must be ASCII; body can be UTF-8 or other encoding.
' PARAMS: U01 (socket handle), N01 (status code), S01 (status text), S02 (headers), S03 (body or chunk source), T01 (timeout in seconds), B01 (chunked encoding flag - %TRUE to enable).
' RETURNS: %TRUE on success, %FALSE on send error.
' * * * SEALED * * *
FUNCTION NW_SendHttpResponse(BYVAL U01 AS DWORD, BYVAL N01 AS LONG, BYVAL S01 AS STRING, BYREF S02 AS STRING, BYREF S03 AS STRING, BYVAL T01 AS LONG, BYVAL B01 AS LONG) COMMON AS LONG
    LOCAL E01 AS STRING
    LOCAL E02 AS LONG
    LOCAL E03 AS LONG
    LOCAL E04 AS STRING ' Chunk data
    LOCAL E05 AS LONG   ' Chunk length
    LOCAL E06 AS STRING ' Chunk header
    ' Validate inputs
    IF N01 < 100 OR N01 > 599 THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_SendHttpResponse: Invalid status code " & STR$(N01) & " on socket " & STR$(U01))
        END IF
        FUNCTION = %FALSE
        EXIT FUNCTION
    END IF
    IF LEN(S01) = 0 THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_SendHttpResponse: Empty status text on socket " & STR$(U01))
        END IF
        FUNCTION = %FALSE
        EXIT FUNCTION
    END IF
    ' Check for non-ASCII characters in statusText and headers (HTTP requires ASCII)
    IF INSTR(S01, ANY CHR$(128 TO 255)) THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_DEBUG, "NW_SendHttpResponse: Non-ASCII characters detected in status text on socket " & STR$(U01))
        END IF
    END IF
    IF LEN(S02) > 0 AND INSTR(S02, ANY CHR$(128 TO 255)) THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_INFO, "NW_SendHttpResponse: Non-ASCII characters detected in headers on socket " & STR$(U01))
        END IF
    END IF
    ' Construct headers
    E01 = "HTTP/1.1 " & FORMAT$(N01) & " " & S01 & $CRLF
    IF B01 THEN
        E01 &= "Transfer-Encoding: chunked" & $CRLF
    ELSE
        E01 &= "Content-Length: " & STR$(LEN(S03)) & $CRLF
    END IF
    E01 &= S02
    IF RIGHT$(S02, 2) <> $CRLF THEN E01 &= $CRLF
    E01 &= $CRLF
    ' Send headers
    E02 = NW_Send(U01, E01, T01)
    IF E02 = %NW_SOCKET_ERROR THEN
        E03 = INT_GetAndStoreError() ' <--- MODIFIED 1 (Use stored error from NW_Send)
        IF g_MCP_LogLevel > 0 THEN
            LOCAL errMsg AS STRING
            SELECT CASE E03
                CASE %WSAEWOULDBLOCK
                    errMsg = "Non-blocking operation would block"
                CASE %WSAENOTCONN
                    errMsg = "Socket not connected"
                CASE %WSAECONNRESET
                    errMsg = "Connection reset by peer"
                CASE ELSE
                    errMsg = "NW_Send failed for headers"
            END SELECT
            MCP_Log(%MCP_LOG_ERROR, "NW_SendHttpResponse: " & errMsg & " on socket " & STR$(U01) & ", WSAError " & STR$(E03))
        END IF
        FUNCTION = %FALSE
        EXIT FUNCTION
    END IF
    ' Send body
    IF LEN(S03) > 0 THEN
        IF B01 THEN
            ' Chunked encoding: send body in chunks
            LOCAL chunkSize AS LONG
            LOCAL startPos AS LONG
            LOCAL chunkData AS STRING
            chunkSize = 65536 ' 64KB chunks
            startPos = 1
            WHILE startPos <= LEN(S03)
                chunkData = MID$(S03, startPos, chunkSize)
                E05 = LEN(chunkData)
                IF E05 = 0 THEN EXIT LOOP

                ' Send chunk header (hex length + CRLF)
                E06 = HEX$(E05) & $CRLF
                E02 = NW_Send(U01, E06, T01)
                IF E02 = %NW_SOCKET_ERROR THEN
                    FUNCTION = %FALSE
                    EXIT FUNCTION
                END IF

                ' Send chunk data AND trailing CRLF
                E02 = NW_Send(U01, chunkData & $CRLF, T01) ' CORRECTED: Added $CRLF after data
                IF E02 = %NW_SOCKET_ERROR THEN
                    FUNCTION = %FALSE
                    EXIT FUNCTION
                END IF

                startPos = startPos + E05
            WEND
            ' Send last chunk (0-length chunk)
            E06 = "0" & $CRLF & $CRLF
            E02 = NW_Send(U01, E06, T01)
            IF E02 = %NW_SOCKET_ERROR THEN
                FUNCTION = %FALSE
                EXIT FUNCTION
            END IF
        ELSE
            ' Non-chunked: send full body
            E02 = NW_Send(U01, S03, T01)
            IF E02 = %NW_SOCKET_ERROR THEN
                E03 = INT_GetAndStoreError() ' <--- MODIFIED 2 (Use stored error from NW_Send)
                IF g_MCP_LogLevel > 0 THEN
                    LOCAL errMsg2 AS STRING
                    SELECT CASE E03
                        CASE %WSAEWOULDBLOCK
                            errMsg2 = "Non-blocking operation would block"
                        CASE %WSAENOTCONN
                            errMsg2 = "Socket not connected"
                        CASE %WSAECONNRESET
                            errMsg2 = "Connection reset by peer"
                        CASE ELSE
                            errMsg2 = "NW_Send failed for body"
                    END SELECT
                    MCP_Log(%MCP_LOG_ERROR, "NW_SendHttpResponse: " & errMsg2 & " on socket " & STR$(U01) & ", WSAError " & STR$(E03))
                END IF
                FUNCTION = %FALSE
                EXIT FUNCTION
            END IF
        END IF
    END IF
    IF g_MCP_LogLevel > 1 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_SendHttpResponse: Sent headers (" & STR$(LEN(E01)) & " bytes) and body (" & STR$(LEN(S03)) & " bytes) for status code " & STR$(N01) & " on socket " & STR$(U01))
    END IF
    ' At end of function, before FUNCTION = %TRUE
    MCP_Log(%MCP_LOG_INFO, "NW_SendHttpResponse: Success on socket " & STR$(U01))
    FUNCTION = %TRUE
END FUNCTION


'===============================================================================
'
'===============================================================================
' PURPOSE: Sends a string of text followed by a CRLF line ending.
' UNICODE: Byte-stream safe, same as NW_Send. Assumes text is properly encoded.
' PARAMS: socketHandle (socket handle), text (text to send), timeoutSec (timeout in seconds).
' RETURNS: Number of bytes sent, or %NW_SOCKET_ERROR on failure.
' * * * SEALED * * *
FUNCTION NW_Print(BYVAL socketHandle AS DWORD, BYREF TEXT AS STRING, BYVAL timeoutSec AS LONG) COMMON AS LONG
    REGISTER R01 AS LONG
    LOCAL dataToSend AS STRING
    LOCAL maxBufferSize AS LONG

    ' Validate input length
    maxBufferSize = 1048576 ' 1MB limit, consistent with NW_Send
    IF LEN(TEXT) > (maxBufferSize - LEN($CRLF)) THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_Print: Text length " & STR$(LEN(TEXT)) & " exceeds max " & STR$(maxBufferSize - LEN($CRLF)) & " on socket " & STR$(socketHandle))
        END IF
        R01= %NW_SOCKET_ERROR
        GOTO enx
    END IF

    ' Append CRLF and send
    dataToSend = TEXT & $CRLF
    R01 = NW_Send(socketHandle, dataToSend, timeoutSec)

    ' Log error if send fails
    IF R01 = %NW_SOCKET_ERROR AND g_MCP_LogLevel > 0 THEN
        MCP_Log(%MCP_LOG_ERROR, "NW_Print: Failed to send data on socket " & STR$(socketHandle) & ", WSAError " & STR$(WSAGetLastError()))
    END IF
    enx:
    FUNCTION=R01
END FUNCTION

'===============================================================================
' PURPOSE: Sends a Unicode WSTRING by transparently encoding it to UTF-8.
' UNICODE: Fully Unicode safe. Handles WSTRING data directly.
' PARAMS: socketHandle (socket handle), text (WSTRING data to send), timeoutSec (timeout in seconds).
' RETURNS: Number of bytes sent, or %NW_SOCKET_ERROR on failure.
FUNCTION NW_SendW(BYVAL socketHandle AS DWORD, BYVAL TEXT AS WSTRING, BYVAL timeoutSec AS LONG) COMMON AS LONG
    REGISTER R01 AS LONG
    LOCAL utf8Data AS STRING
    LOCAL maxBufferSize AS LONG

    ' Validate input
    IF LEN(TEXT) = 0 THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_DEBUG, "NW_SendW: Empty WSTRING on socket " & STR$(socketHandle))
        END IF
        R01 = 0: GOTO enx
    END IF

    ' Convert WSTRING to UTF-8
    utf8Data = HTP_WideToUtf8(TEXT)
    IF LEN(utf8Data) = 0 AND LEN(TEXT) > 0 THEN ' Check if conversion failed on non-empty string
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_SendW: UTF-8 conversion failed on socket " & STR$(socketHandle))
        END IF
        R01 = %NW_SOCKET_ERROR: GOTO enx
    END IF

    ' Validate encoded length
    maxBufferSize = 1048576 ' 1MB limit, consistent with NW_Send
    IF LEN(utf8Data) > maxBufferSize THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_SendW: UTF-8 data length " & STR$(LEN(utf8Data)) & " exceeds max " & STR$(maxBufferSize) & " on socket " & STR$(socketHandle))
        END IF
        R01 = %NW_SOCKET_ERROR: GOTO enx
    END IF

    ' --- CORRECTED LOGIC ---
    ' Send UTF-8 encoded data and store the result in R01
    R01 = NW_Send(socketHandle, utf8Data, timeoutSec)

    ' Log error if send fails
    IF R01 = %NW_SOCKET_ERROR AND g_MCP_LogLevel > 0 THEN
        MCP_Log(%MCP_LOG_ERROR, "NW_SendW: Failed to send data on socket " & STR$(socketHandle) & ", WSAError " & STR$(WSAGetLastError()))
    END IF

enx:
    FUNCTION = R01 ' Return the value stored in R01
END FUNCTION

'===============================================================================
' Part 4: Asynchronous and Non-Blocking Operations (I/O Control)
'===============================================================================

' PURPOSE: Enables or disables non-blocking mode on a socket.
' UNICODE: Not applicable; this function manipulates a socket's state.
' PARAMS:  socketHandle (SOCKET In), enable (LONG In - %TRUE or %FALSE).
' RETURNS: 0 on success, %NW_SOCKET_ERROR on failure.
' * * * SEALED * * *
FUNCTION NW_SetNonBlocking(BYVAL socketHandle AS DWORD, BYVAL U02 AS LONG) COMMON AS LONG
    LOCAL arg AS DWORD
    arg = IIF(U02 <> 0, 1, 0)
    FUNCTION = ioctlsocket(socketHandle, %FIONBIO, arg)
END FUNCTION

' PURPOSE: Associates network events (read, write, close) on a socket with a window message.
' UNICODE: Not applicable; deals with handles and system messages.
' PARAMS:  socketHandle (SOCKET), hWnd (Window Handle), wMsg, lEvent flags.
' RETURNS: 0 on success, %NW_SOCKET_ERROR on failure.
FUNCTION NW_AsyncSelect(BYVAL socketHandle AS DWORD, BYVAL hWnd AS DWORD, BYVAL wMsg AS DWORD, BYVAL lEvent AS LONG) COMMON AS LONG
    FUNCTION = WSAAsyncSelect(socketHandle, hWnd, wMsg, lEvent)
END FUNCTION

'===============================================================================
' Part 5: Asynchronous Data Transfer
'===============================================================================

' PURPOSE: Sends a buffer of data using WSASend (synchronous, non-overlapped).
' UNICODE: Byte-stream safe. Transmits raw bytes of any encoding (e.g., UTF-8).
' PARAMS: socketHandle (socket handle), data (STRING to send), timeoutSec (timeout in seconds).
' RETURNS: Number of bytes sent on success, or %NW_SOCKET_ERROR on failure.
FUNCTION NW_SendEx(BYVAL socketHandle AS DWORD, BYREF U02 AS STRING, BYVAL timeoutSec AS LONG) COMMON AS LONG
    LOCAL E01 AS WSABUF
    LOCAL bytesSent AS DWORD
    LOCAL result AS LONG
    LOCAL timeoutMarker AS DOUBLE
    LOCAL maxBufferSize AS LONG

    ' Validate input
    maxBufferSize = 1048576 ' 1MB limit, consistent with NW_Send
    IF LEN(U02) <= 0 OR LEN(U02) > maxBufferSize THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_SendEx: Invalid data length " & STR$(LEN(U02)) & " on socket " & STR$(socketHandle))
        END IF
        FUNCTION = %NW_SOCKET_ERROR
        EXIT FUNCTION
    END IF

    ' Set timeout
    IF timeoutSec <= 0 THEN timeoutSec = %NW_TIMEOUT_DEFAULT / 1000
    timeoutMarker = TIMER + timeoutSec

    ' Check for timeout (for consistency with non-blocking handling)
    IF TIMER > timeoutMarker THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_SendEx: Timeout on socket " & STR$(socketHandle))
        END IF
        FUNCTION = %NW_SOCKET_ERROR
        EXIT FUNCTION
    END IF

    ' Prepare WSABUF structure
    E01.dlen = LEN(U02)
    E01.buf = STRPTR(U02)

    ' Send data using WSASend
    result = WSASend(socketHandle, E01, 1, bytesSent, 0, BYVAL %NULL, BYVAL %NULL)

    IF result = 0 THEN
        FUNCTION = bytesSent
    ELSE
        ' Handle error, including WSAEWOULDBLOCK for non-blocking sockets
        IF WSAGetLastError() = %WSAEWOULDBLOCK THEN
            IF g_MCP_LogLevel > 0 THEN
                MCP_Log(%MCP_LOG_INFO, "NW_SendEx: No data sent (WSAEWOULDBLOCK) on socket " & STR$(socketHandle))
            END IF
            FUNCTION = 0 ' No data sent, but not an error
        ELSE
            IF g_MCP_LogLevel > 0 THEN
                MCP_Log(%MCP_LOG_ERROR, "NW_SendEx: Error on socket " & STR$(socketHandle) & ", WSAError " & STR$(WSAGetLastError()))
            END IF
            FUNCTION = %NW_SOCKET_ERROR
        END IF
    END IF
END FUNCTION
'--------------------------------------------------------------------------------------------------
'===============================================================================
' ########################################################################
' FUNCTION: NW_SendFile (optimized, chunked, single-exit)
'-
' Purpose:
' Sends a file over a socket in chunks, using NW_Send for reliability.
'
' Parameters:
' U01 (BYVAL DWORD) - Socket handle.
' S01 (BYVAL STRING) - Pathname of the file to send.
' T01 (BYVAL LONG) - Timeout in seconds (passed to NW_Send).
'
' Returns:
' >= 0 : Total bytes successfully sent.
' -1 : %NW_SOCKET_ERROR on failure (file not found, read error, NW_Send error).
'
' Notes:
' - Sends files in 64KB chunks for efficiency.
' - Uses NW_Send for each chunk, inheriting its timeout and error handling.
' - Single-exit pattern with label 'cleanup:' and result variable R01.
' ########################################################################
' PURPOSE: Sends the contents of a file over a socket.
' UNICODE: File path is ASCII/UTF-8, data is sent as raw bytes.
' PARAMS: U01 (socket), S01 (file path), T01 (timeout for NW_Send).
' RETURNS: Total bytes sent, or %NW_SOCKET_ERROR on failure.
FUNCTION NW_SendFile(BYVAL U01 AS DWORD, BYVAL S01 AS STRING, BYVAL T01 AS LONG) COMMON AS LONG
    LOCAL E01 AS DOUBLE
    LOCAL E02 AS LONG
    LOCAL E03 AS STRING
    LOCAL E04 AS LONG
    LOCAL E05 AS LONG
    LOCAL E06 AS LONG
    LOCAL E08 AS LONG
    LOCAL R01 AS LONG
    LOCAL T02 AS QUAD
    LOCAL T03 AS LONG
    R01 = %NW_SOCKET_ERROR
    E02 = 0
    E06 = 65536
    IF LEN(S01) = 0 THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_SendFile: Empty file path on socket " & STR$(U01))
        END IF
        GOTO cleanup
    END IF
    E04 = FREEFILE
    OPEN S01 FOR BINARY ACCESS READ AS E04
    IF ERR THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_SendFile: Failed to open file '" & S01 & "' on socket " & STR$(U01) & ", Error " & STR$(ERR))
        END IF
        GOTO cleanup
    END IF
    IF T01 <= 0 THEN T01 = %NW_TIMEOUT_DEFAULT / 1000
    E01 = TIMER + T01
    IF g_MCP_LogLevel > 1 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_SendFile: Sending file '" & S01 & "' on socket " & STR$(U01) & ", timeout " & STR$(T01) & " seconds")
    END IF
    DO
        IF TIMER > E01 THEN
            IF g_MCP_LogLevel > 0 THEN
                MCP_Log(%MCP_LOG_ERROR, "NW_SendFile: Timeout after sending " & STR$(E02) & " bytes on socket " & STR$(U01))
            END IF
            GOTO cleanup
        END IF
        IF LOC(E04) > LOF(E04) THEN EXIT DO
        T02 = LOF(E04) - LOC(E04) + 1
        IF T02 <= 0 THEN EXIT DO
        T03 = MIN(T02, E06)
        E03 = SPACE$(T03)
        ' --- CORRECTED SYNTAX ---
        ' Use LOC(E04) + 1 as the record number to read from the current position.
        ' Since it's a binary file, the "record" is just a byte.
        GET E04, LOC(E04) + 1, E03
        ' --- END CORRECTION ---
        IF ERR THEN
            IF g_MCP_LogLevel > 0 THEN
                MCP_Log(%MCP_LOG_ERROR, "NW_SendFile: Failed to read file '" & S01 & "' on socket " & STR$(U01) & ", Error " & STR$(ERR))
            END IF
            GOTO cleanup
        END IF
        E05 = NW_Send(U01, E03, T01)
        IF E05 = %NW_SOCKET_ERROR THEN
            E08 = WSAGetLastError()
            IF E08 = %WSAEWOULDBLOCK THEN
                SLEEP 100 ' Increased for large file stability
                IF g_MCP_LogLevel > 2 THEN
                    MCP_Log(%MCP_LOG_DEBUG, "NW_SendFile: WSAEWOULDBLOCK, retrying after sending " & STR$(E02) & " bytes on socket " & STR$(U01))
                END IF
                ' Rewind for retry: Set file pointer back by the amount we tried to send
                SEEK E04, LOC(E04) - LEN(E03)
                ITERATE LOOP
            END IF
            IF g_MCP_LogLevel > 0 THEN
                LOCAL errMsg AS STRING
                SELECT CASE E08
                    CASE %WSAENOTCONN
                        errMsg = "Socket not connected"
                    CASE %WSAECONNRESET
                        errMsg = "Connection reset by peer"
                    CASE ELSE
                        errMsg = "NW_Send failed, WSAError " & STR$(E08)
                END SELECT
                MCP_Log(%MCP_LOG_ERROR, "NW_SendFile: " & errMsg & " after sending " & STR$(E02) & " bytes on socket " & STR$(U01))
            END IF
            GOTO cleanup
        END IF
        E02 = E02 + E05
        IF E05 < LEN(E03) THEN
            IF g_MCP_LogLevel > 0 THEN
                MCP_Log(%MCP_LOG_INFO, "NW_SendFile: Partial send of " & STR$(E05) & "/" & STR$(LEN(E03)) & " bytes on socket " & STR$(U01))
            END IF
            ' Rewind for the unsent portion
            SEEK E04, LOC(E04) - (LEN(E03) - E05)
        END IF
        IF g_MCP_LogLevel > 2 THEN
            MCP_Log(%MCP_LOG_DEBUG, "NW_SendFile: Sent " & STR$(E05) & " bytes chunk from file '" & S01 & "', total " & STR$(E02) & " on socket " & STR$(U01))
        END IF
    LOOP
    R01 = E02
    IF g_MCP_LogLevel > 1 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_SendFile: Successfully sent " & STR$(E02) & " bytes from file '" & S01 & "' on socket " & STR$(U01))
    END IF
cleanup:
    IF E04 THEN CLOSE E04
    FUNCTION = R01
END FUNCTION
'===============================================================================
'===============================================================================
' Part 6: Socket Options and Information (Corrected)
'===============================================================================

' PURPOSE: Sets the send timeout for a socket in milliseconds.
' UNICODE: Not applicable; this function manipulates a socket's state.
' PARAMS:  U01 (socket handle), U02 (timeout in ms).
' RETURNS: 0 on success, %NW_SOCKET_ERROR on failure.
FUNCTION NW_SetSendTimeout(BYVAL U01 AS DWORD, BYVAL U02 AS LONG) COMMON AS LONG
    FUNCTION = setsockopt(U01, %SOL_SOCKET, %SO_SNDTIMEO, BYVAL VARPTR(U02), SIZEOF(LONG))
END FUNCTION
'===============================================================================

' PURPOSE: Sets a specific, strongly-typed socket option for the receive timeout.
' UNICODE: Not applicable; deals with socket properties.
' PARAMS:  U01 (socket handle), U02 (timeout in milliseconds).
' RETURNS: 0 on success, %NW_SOCKET_ERROR on failure.
FUNCTION NW_SetRecvTimeout(BYVAL U01 AS DWORD, BYVAL U02 AS LONG) COMMON AS LONG
    ' Calls the official 'setsockopt' API function from WinSock2.inc
    FUNCTION = setsockopt(U01, %SOL_SOCKET, %SO_RCVTIMEO, BYVAL VARPTR(U02), SIZEOF(LONG))
END FUNCTION
'===============================================================================
' PURPOSE: Gets a specific, strongly-typed socket option for the receive timeout.
' UNICODE: Not applicable; deals with socket properties.
' PARAMS:  U01 (socket handle), U02 (receives timeout in milliseconds).
' RETURNS: 0 on success, %NW_SOCKET_ERROR on failure.
FUNCTION NW_GetRecvTimeout(BYVAL U01 AS DWORD, BYREF U02 AS LONG) COMMON AS LONG
    LOCAL T01_Len AS LONG
    T01_Len = SIZEOF(LONG)
    ' Calls the official 'getsockopt' API function from WinSock2.inc
    FUNCTION = getsockopt(U01, %SOL_SOCKET, %SO_RCVTIMEO, U02, T01_Len)
END FUNCTION
'===============================================================================

' PURPOSE: Enables or disables the SO_REUSEADDR option on a socket.
' UNICODE: Not applicable; this function manipulates a socket's state.
' PARAMS:  U01 (socket handle), U02 (flag - %TRUE to enable).
' RETURNS: 0 on success, %NW_SOCKET_ERROR on failure.
' * * * SEALED * * *
FUNCTION NW_SetReuseAddr(BYVAL U01 AS DWORD, BYVAL U02 AS LONG) COMMON AS LONG
    FUNCTION = setsockopt(U01, %SOL_SOCKET, %SO_REUSEADDR, BYVAL VARPTR(U02), SIZEOF(LONG))
END FUNCTION
'===============================================================================
' PURPOSE: Sets the SO_LINGER option on a socket to control closing behavior.
' UNICODE: Not applicable; this function manipulates a socket's state.
' PARAMS:  U01 (socket handle), U02 (linger UDT).
' RETURNS: 0 on success, %NW_SOCKET_ERROR on failure.
' * * * SEALED * * *
FUNCTION NW_SetLinger(BYVAL U01 AS DWORD, BYREF U02 AS LINGER) COMMON AS LONG
    FUNCTION = setsockopt(U01, %SOL_SOCKET, %SO_LINGER, BYVAL VARPTR(U02), SIZEOF(LINGER))
END FUNCTION
'===============================================================================
' PURPOSE: Retrieves the local address and port for a bound socket.
' UNICODE: Not applicable; returns a binary address structure.
' PARAMS: U01 (socket handle), E01_Addr (sockaddr_in Out).
' RETURNS: 0 on success, %NW_SOCKET_ERROR on failure.
FUNCTION NW_GetLocalInfo(BYVAL U01 AS DWORD, BYREF E01_Addr AS sockaddr_in) COMMON AS LONG
    LOCAL T01_AddrLen AS LONG
    T01_AddrLen = SIZEOF(E01_Addr)
    FUNCTION = WS_getsockname(U01, VARPTR(E01_Addr), T01_AddrLen)
END FUNCTION


'===============================================================================
' Part 7: Error Handling and Utilities
'===============================================================================
' PURPOSE: Retrieves the last Winsock error code FOR THIS THREAD.
' UNICODE: Not applicable; no string operations.
' PARAMS: None.
' RETURNS: Last error code (or 0 if clear).
' NOTE: This function is now PURELY an accessor for our internal
'       thread-local variable (g_NW_LastThreadError).
' * * * SEALED * * *
FUNCTION NW_GetLastError() COMMON AS LONG
    FUNCTION = g_NW_LastThreadError
END FUNCTION
'-------------------------------------------------------------------------------
'########################################################################
' FUNCTION: INT_GetAndStoreError (Internal Helper)
'------------------------------------------------------------------------
' Purpose:
'   The internal, central function for all error retrieval.
'   It calls the real Winsock API, saves the error to our persistent
'   thread-local variable, and returns the error code.
'
' Parameters: None.
' Returns: (LONG) - The error code retrieved from Winsock.
'########################################################################
FUNCTION INT_GetAndStoreError() COMMON AS LONG
    LOCAL E01 AS LONG
    E01 = WSAGetLastError()      ' 1. Get the REAL API error
    g_NW_LastThreadError = E01 ' 2. Save it to our persistent variable
    FUNCTION = E01               ' 3. Return it for immediate use
END FUNCTION
'-------------------------------------------------------------------------------
' PURPOSE: Sets the last Winsock error code FOR THIS THREAD's internal variable.
' UNICODE: Not applicable; no string operations.
' PARAMS: U01 (LONG In) - Error code to set.
' RETURNS: None.
' NOTE: This updates both the actual WinAPI error state AND our persistent
'       thread-local variable (g_NW_LastThreadError).
' * * * SEALED * * *
'-------------------------------------------------------------------------------
SUB NW_SetLastError(BYVAL U01 AS LONG) COMMON
     WSASetLastError(U01)       ' 1. Set the actual thread error state
     g_NW_LastThreadError = U01 ' 2. Set our persistent variable
END SUB
'-------------------------------------------------------------------------------
' PURPOSE: Manually clears the persistent thread-local error variable.
' UNICODE: Not applicable.
' PARAMS: None.
' RETURNS: None.
' NOTE: This MUST be called by the application after handling an error,
'       otherwise NW_GetLastError() will continue to return the old, stale error.
'-------------------------------------------------------------------------------
SUB NW_ClearError() COMMON
     g_NW_LastThreadError = 0
END SUB
'-------------------------------------------------------------------------------
' PURPOSE: Enables or disables the library's internal debug logging features.
' UNICODE: Not applicable; manipulates a global flag.
' PARAMS:  enable (LONG In) - %TRUE to enable, %FALSE to disable.
' RETURNS: None.
SUB NW_EnableDebugLogging(BYVAL U02 AS LONG) COMMON
    g_NW_DebugMode = IIF(U02 <> 0, %TRUE, %FALSE)
END SUB
'-------------------------------------------------------------------------------
' PURPOSE: Enables or disables hex dumping of data when debug logging is active.
' UNICODE: Not applicable; manipulates a global flag.
' PARAMS:  enable (LONG In) - %TRUE to enable, %FALSE to disable.
' RETURNS: None.
SUB NW_EnableHexDump(BYVAL U02 AS LONG) COMMON
    g_NW_HexDumpEnabled = IIF(U02 <> 0, %TRUE, %FALSE)
    MCP_SetHexDump(g_NW_HexDumpEnabled)
END SUB
'-------------------------------------------------------------------------------
'########################################################################
' FUNCTION: NW_GaiErrorText
'------------------------------------------------------------------------
' Purpose : Human-readable text for getaddrinfo() error codes.
' Params  : c (LONG) - nonzero code returned by WS_getaddrinfo
' Return  : STRING (never empty)
' Note    : Uses numeric WSA codes so it works even if constants differ.
' * * * SEALED * * *
'########################################################################
FUNCTION NW_GaiErrorText(BYVAL c AS LONG) COMMON AS STRING
    LOCAL s AS STRING
    SELECT CASE c
        CASE 11001 : s = "Host not found"                          ' %WSAHOST_NOT_FOUND
        CASE 11002 : s = "Temporary failure in name resolution"    ' %WSATRY_AGAIN
        CASE 11003 : s = "Non-recoverable name resolution error"   ' %WSANO_RECOVERY
        CASE 11004 : s = "No data record of requested type"        ' %WSANO_DATA
        CASE 10047 : s = "Address family not supported"            ' %WSAEAFNOSUPPORT
        CASE 10044 : s = "Socket type not supported"               ' %WSAESOCKTNOSUPPORT
        CASE 10036 : s = "Operation now in progress"               ' %WSAEINPROGRESS
        CASE 10055 : s = "No buffer space available"               ' %WSAENOBUFS
        CASE ELSE : s = "getaddrinfo error " & FORMAT$(c)
    END SELECT
enx:
    FUNCTION = s
END FUNCTION

'===============================================================================
' Part 8: Host and Address Resolution
'===============================================================================
'===============================================================================
' PURPOSE: Resolves a hostname (e.g., "google.com") to an IPv4 or IPv6 address string.
' UNICODE: Hostnames are ANSI/ASCII only as per internet standards.
' PARAMS: S01 (STRING In), S02 (STRING Out), T01 (timeout in seconds).
' RETURNS: %TRUE on success, %FALSE on failure.
' * * * SEALED * * *
FUNCTION NW_ResolveHost(BYVAL S01 AS STRING, BYREF S02 AS STRING, BYVAL T01 AS LONG) COMMON AS LONG
    LOCAL E01 AS ADDRINFOA
    LOCAL E02 AS ADDRINFOA PTR
    LOCAL E03 AS ADDRINFOA PTR
    LOCAL E04 AS SOCKADDR PTR
    LOCAL E05 AS LONG
    LOCAL E06 AS ASCIIZ * 46
    LOCAL E07 AS DWORD
    LOCAL E08 AS DOUBLE

    ' Initialize output
    S02 = ""

    ' Set timeout
    IF T01 <= 0 THEN T01 = %NW_TIMEOUT_DEFAULT / 1000
    E08 = TIMER + T01

    ' Check for timeout
    IF TIMER > E08 THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_ResolveHost: Timeout for hostname " & S01)
        END IF
        FUNCTION = %FALSE
        EXIT FUNCTION
    END IF

    ' Validate input
    IF LEN(S01) = 0 THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_ResolveHost: Empty hostname")
        END IF
        FUNCTION = %FALSE
        EXIT FUNCTION
    END IF

    ' Set hints for IPv4 or IPv6 and TCP
    FILLMEMORY(VARPTR(E01), SIZEOF(E01), 0) ' Clear hints structure
    E01.ai_family = %AF_UNSPEC
    E01.ai_socktype = %SOCK_STREAM

    ' Resolve hostname
    E05 = WS_getaddrinfo(STRPTR(S01), BYVAL %NULL, VARPTR(E01), E02) ' Pass E02 (ptr variable) ByRef
    IF E05 = 0 THEN
        E03 = E02
        DO WHILE E03 <> %NULL
            IF @E03.ai_family = %AF_INET OR @E03.ai_family = %AF_INET6 THEN
                E04 = @E03.ai_addr ' E04 is a SOCKADDR PTR (a DWORD value)
                E07 = 46 ' Buffer size for IPv4 (16) or IPv6 (46)

                ' CORRECTED CALL: Pass E04 (the pointer value) BYVAL, not VARPTR(E04)
                IF WS_AddrToStringA(E04, @E03.ai_addrlen, BYVAL %NULL, E06, E07) = 0 THEN
                    S02 = E06
                    IF LEN(S02) = 0 THEN
                        IF g_MCP_LogLevel > 0 THEN
                            MCP_Log(%MCP_LOG_ERROR, "NW_ResolveHost: Failed to convert WS_AddrToStringA result to string for hostname " & S01)
                        END IF
                        WS_freeaddrinfo(E02)
                        FUNCTION = %FALSE
                        EXIT FUNCTION
                    END IF
                    IF g_MCP_LogLevel > 1 THEN
                        MCP_Log(%MCP_LOG_DEBUG, "NW_ResolveHost: Resolved hostname " & S01 & " to IP " & S02)
                    END IF
                    WS_freeaddrinfo(E02)
                    FUNCTION = %TRUE
                    EXIT FUNCTION
                ELSE
                    IF g_MCP_LogLevel > 0 THEN
                        MCP_Log(%MCP_LOG_ERROR, "NW_ResolveHost: WS_AddrToStringA failed for hostname " & S01 & ", WSAError " & STR$(WSAGetLastError()))
                    END IF
                END IF
            END IF
            E03 = @E03.ai_next
        LOOP
        WS_freeaddrinfo(E02)
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_ResolveHost: No IPv4 or IPv6 address found for hostname " & S01)
        END IF
        FUNCTION = %FALSE
    ELSE
        IF g_MCP_LogLevel > 0 THEN
           MCP_Log(%MCP_LOG_ERROR, "NW_ResolveHost: WS_getaddrinfo failed for hostname " & S01 &" -> " & NW_GaiErrorText(E05) & " (" & STR$(E05) & ")")
        END IF
        FUNCTION = %FALSE
    END IF
END FUNCTION
'===============================================================================
'
'===============================================================================
'===============================================================================

' PURPOSE: Resolves an IPv4 or IPv6 address string to its corresponding hostname (rDNS).
' UNICODE: Hostnames are ANSI/ASCII only as per internet standards.
' PARAMS: S01 (STRING In), S02 (STRING Out), T01 (timeout in seconds).
' RETURNS: %TRUE on success, %FALSE on failure.
' * * * SEALED * * *
FUNCTION NW_ReverseResolve (BYVAL S01 AS STRING, BYREF S02 AS STRING, BYVAL T01 AS LONG) COMMON AS LONG
    LOCAL E01 AS ADDRINFOA
    LOCAL E02 AS ADDRINFOA PTR
    LOCAL E03 AS LONG
    LOCAL E04 AS ASCIIZ * 256
    LOCAL E05 AS DOUBLE
    LOCAL R01 AS LONG
    ' Initialize output and result
    S02 = ""
    R01 = %FALSE
    ' Set timeout
    IF T01 <= 0 THEN T01 = %NW_TIMEOUT_DEFAULT / 1000
    E05 = TIMER + T01
    IF g_MCP_LogLevel > 1 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_ReverseResolve: Timeout set to " & STR$(T01) & " seconds for IP " & S01)
        X_AU "[DEBUG] NW_ReverseResolve: Timeout set to " & STR$(T01) & " seconds for IP " & S01
    END IF
    ' Check for timeout
    IF TIMER > E05 THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_ReverseResolve: Timeout for IP " & S01)
            X_AU "[ERROR] NW_ReverseResolve: Timeout for IP " & S01
        END IF
        GOTO enx
    END IF
    ' Validate input
    IF LEN(S01) = 0 THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_ReverseResolve: Empty IP address")
            X_AU "[ERROR] NW_ReverseResolve: Empty IP address"
        END IF
        GOTO enx
    END IF
    ' Set hints for numeric host
    FILLMEMORY(VARPTR(E01), SIZEOF(E01), 0)
    E01.ai_family = %AF_UNSPEC
    E01.ai_socktype = 0
    E01.ai_flags = %AI_NUMERICHOST
    IF g_MCP_LogLevel > 2 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_ReverseResolve: Hints set: family=" & STR$(E01.ai_family) & ", socktype=" & STR$(E01.ai_socktype) & ", flags=" & STR$(E01.ai_flags))
        X_AU "[DEBUG] NW_ReverseResolve: Hints set: family=" & STR$(E01.ai_family) & ", socktype=" & STR$(E01.ai_socktype) & ", flags=" & STR$(E01.ai_flags)
    END IF
    ' Resolve address to hostname
    E03 = WS_getaddrinfo(STRPTR(S01), %NULL, VARPTR(E01), E02)
    IF E03 <> 0 THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_ReverseResolve: WS_getaddrinfo failed for IP " & S01 & " -> " & NW_GaiErrorText(E03) & " (" & STR$(E03) & ")")
            X_AU "[ERROR] NW_ReverseResolve: WS_getaddrinfo failed for IP " & S01 & ", WSAError " & STR$(E03) & " (" & ERROR$(E03) & ")"
        END IF
        GOTO enx
    END IF
    IF g_MCP_LogLevel > 1 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_ReverseResolve: getaddrinfo succeeded for IP " & S01)
        X_AU "[DEBUG] NW_ReverseResolve: getaddrinfo succeeded for IP " & S01
    END IF
    ' Get hostname
    E03 = WS_getnameinfo(@E02.ai_addr, @E02.ai_addrlen, E04, SIZEOF(E04), BYVAL %NULL, 0, 0)
    IF E03 = 0 THEN
        S02 = E04
        IF LEN(S02) = 0 THEN
            IF g_MCP_LogLevel > 0 THEN
                MCP_Log(%MCP_LOG_ERROR, "NW_ReverseResolve: Empty hostname returned for IP " & S01)
                X_AU "[ERROR] NW_ReverseResolve: Empty hostname returned for IP " & S01
            END IF
            GOTO enx
        END IF
        IF g_MCP_LogLevel > 1 THEN
            MCP_Log(%MCP_LOG_DEBUG, "NW_ReverseResolve: Resolved IP " & S01 & " to hostname " & S02)
            X_AU "[DEBUG] NW_ReverseResolve: Resolved IP " & S01 & " to hostname " & S02
        END IF
        R01 = %TRUE
    ELSE
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_ReverseResolve: WS_getnameinfo failed for IP " & S01 & ", WSAError " & STR$(E03) & " (" & ERROR$(E03) & ")")
            X_AU "[ERROR] NW_ReverseResolve: WS_getnameinfo failed for IP " & S01 & ", WSAError " & STR$(E03) & " (" & ERROR$(E03) & ")"
        END IF
    END IF
enx:
    ' Cleanup
    IF E02 THEN WS_freeaddrinfo(E02)
    IF g_MCP_LogLevel > 1 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_ReverseResolve: Cleanup complete, result=" & STR$(R01))
        X_AU "[DEBUG] NW_ReverseResolve: Cleanup complete, result=" & STR$(R01)
    END IF
    FUNCTION = R01
END FUNCTION
'===============================================================================
'===============================================================================
' PURPOSE: Converts a SOCKADDR_STORAGE structure to a printable "ip:port" string.
' UNICODE: The output string is ANSI/ASCII as it represents an IP and port.
' PARAMS: E01 (SOCKADDR_STORAGE In), salen (actual address length In), S01 (STRING Out).
' RETURNS: %TRUE on success, %FALSE on failure.
' * * * SEALED * * *
FUNCTION NW_AddrToString(BYVAL E01 AS SOCKADDR_STORAGE, BYVAL salen AS LONG, BYREF S01 AS STRING) COMMON AS LONG
    LOCAL E02 AS ASCIIZ * 46
    LOCAL E03 AS DWORD
    LOCAL E04 AS LONG
    ' Initialize output
    S01 = ""
    ' Validate input
    IF E01.ss_family <> %AF_INET AND E01.ss_family <> %AF_INET6 THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_AddrToString: Invalid or unsupported address family " & STR$(E01.ss_family))
        END IF
        FUNCTION = %FALSE
        EXIT FUNCTION
    END IF
    ' Convert address to string
    E03 = SIZEOF(E02) ' Buffer size for IPv4 (16) or IPv6 (46)
    IF WS_AddrToStringA(VARPTR(E01), salen, BYVAL %NULL, E02, E03) = 0 THEN
        S01 = E02
        IF LEN(S01) = 0 THEN
            IF g_MCP_LogLevel > 0 THEN
                MCP_Log(%MCP_LOG_ERROR, "NW_AddrToString: Failed to convert WS_AddrToStringA result to string")
            END IF
            FUNCTION = %FALSE
            EXIT FUNCTION
        END IF
        IF g_MCP_LogLevel > 1 THEN
            MCP_Log(%MCP_LOG_DEBUG, "NW_AddrToString: Converted address to " & S01 & ", family " & STR$(E01.ss_family))
        END IF
        FUNCTION = %TRUE
    ELSE
        E04 = WSAGetLastError()
        IF g_MCP_LogLevel > 0 THEN
            LOCAL errMsg AS STRING
            SELECT CASE E04
                CASE %WSAEINVAL
                    errMsg = "Invalid arguments"
                CASE %WSAEFAULT
                    errMsg = "Invalid address buffer"
                CASE ELSE
                    errMsg = "WS_AddrToStringA failed"
            END SELECT
            MCP_Log(%MCP_LOG_ERROR, "NW_AddrToString: " & errMsg & ", WSAError " & STR$(E04))
        END IF
        FUNCTION = %FALSE
    END IF
END FUNCTION
'===============================================================================
'
'===============================================================================
'===============================================================================
' PURPOSE: Converts a printable "ip:port" string into a SOCKADDR_STORAGE structure.
' UNICODE: The input string should be ANSI/ASCII.
' PARAMS: S01 (STRING In), E01 (SOCKADDR_STORAGE Out), S02 (LONG Out for addrlen), T01 (timeout in seconds).
' RETURNS: %TRUE on success, %FALSE on failure.
' * * * SEALED * * *
FUNCTION NW_StringToAddr (BYVAL S01 AS STRING, BYREF E01 AS SOCKADDR_STORAGE, BYREF S02 AS LONG, BYVAL T01 AS LONG) COMMON AS LONG
    LOCAL L01 AS STRING, L02 AS STRING, N01 AS LONG, N02 AS LONG, N03 AS LONG
    LOCAL E02 AS ADDRINFOA, E03 AS ADDRINFOA PTR, R01 AS LONG, D01 AS DOUBLE
    LOCAL N04 AS DWORD
    ' Initialize output and result
    FILLMEMORY(VARPTR(E01), SIZEOF(E01), 0)
    S02 = 0
    R01 = %FALSE
    ' Set timeout
    IF T01 <= 0 THEN T01 = %NW_TIMEOUT_DEFAULT / 1000
    D01 = TIMER + T01
    IF g_MCP_LogLevel > 1 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_StringToAddr: Timeout set to " & STR$(T01) & " seconds for IP:port " & S01)
        X_AU "[DEBUG] NW_StringToAddr: Timeout set to " & STR$(T01) & " seconds for IP:port " & S01
    END IF
    ' Check for timeout
    IF TIMER > D01 THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_StringToAddr: Timeout for IP:port " & S01)
            X_AU "[ERROR] NW_StringToAddr: Timeout for IP:port " & S01
        END IF
        GOTO enx
    END IF
    ' Validate input
    IF LEN(S01) = 0 THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_StringToAddr: Empty IP:port string")
            X_AU "[ERROR] NW_StringToAddr: Empty IP:port string"
        END IF
        GOTO enx
    END IF
    ' Parse [IPv6]:port or IPv4:port or bare literal
    N01 = INSTR(S01, "[")
    N02 = INSTR(S01, "]")
    IF N01 AND N02 THEN
        L01 = MID$(S01, N01 + 1, N02 - N01 - 1)
        N03 = INSTR(N02 + 1, S01, ":")
        IF N03 THEN L02 = MID$(S01, N03 + 1)
    ELSE
        N03 = INSTR(-1, S01, ":") ' last colon (safe for IPv4)
        IF N03 THEN
            L01 = LEFT$(S01, N03 - 1)
            L02 = MID$(S01, N03 + 1)
        ELSE
            L01 = S01
            L02 = ""
        END IF
    END IF
    IF g_MCP_LogLevel > 2 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_StringToAddr: Parsed IP='" & L01 & "', port='" & L02 & "'")
        X_AU "[DEBUG] NW_StringToAddr: Parsed IP='" & L01 & "', port='" & L02 & "'"
    END IF
    ' Set hints
    FILLMEMORY(VARPTR(E02), SIZEOF(E02), 0)
    E02.ai_family = %AF_UNSPEC
    E02.ai_socktype = %SOCK_STREAM
    E02.ai_flags = %AI_NUMERICHOST
    IF LEN(L02) THEN E02.ai_flags = E02.ai_flags OR %AI_NUMERICSERV
    IF g_MCP_LogLevel > 2 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_StringToAddr: Hints set: family=" & STR$(E02.ai_family) & ", socktype=" & STR$(E02.ai_socktype) & ", flags=" & STR$(E02.ai_flags))
        X_AU "[DEBUG] NW_StringToAddr: Hints set: family=" & STR$(E02.ai_family) & ", socktype=" & STR$(E02.ai_socktype) & ", flags=" & STR$(E02.ai_flags)
    END IF
    ' Resolve address
    IF LEN(L02) THEN
        N04 = STRPTR(L02)
    ELSE
        N04 = %NULL
    END IF
    N03 = WS_getaddrinfo(STRPTR(L01), N04, VARPTR(E02), E03)
    IF N03 <> 0 THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_StringToAddr: WS_getaddrinfo failed for IP:port " & S01 & " -> " & NW_GaiErrorText(N03) & " (" & STR$(N03) & ")")
            X_AU "[ERROR] NW_StringToAddr: WS_getaddrinfo failed for IP:port " & S01 & ", WSAError " & STR$(N03) & " (" & ERROR$(N03) & ")"
        END IF
        GOTO enx
    END IF
    IF g_MCP_LogLevel > 1 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_StringToAddr: getaddrinfo succeeded for IP:port " & S01)
        X_AU "[DEBUG] NW_StringToAddr: getaddrinfo succeeded for IP:port " & S01
    END IF
    ' Copy address to output
    S02 = MIN&(SIZEOF(E01), @E03.ai_addrlen)
    CALL MoveMemory(BYVAL VARPTR(E01), BYVAL @E03.ai_addr, S02)
    R01 = %TRUE
enx:
    ' Cleanup
    IF E03 THEN WS_freeaddrinfo(E03)
    IF g_MCP_LogLevel > 1 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_StringToAddr: Cleanup complete, result=" & STR$(R01))
        X_AU "[DEBUG] NW_StringToAddr: Cleanup complete, result=" & STR$(R01)
    END IF
    FUNCTION = R01
END FUNCTION
'===============================================================================
' Part 9: Enhanced High-Level Functions
'===============================================================================
' PURPOSE: Retrieves the remote address (IP string) for a connected socket.
' UNICODE: The output IP string is ANSI/ASCII.
' PARAMS: U01 (socket handle), S01 (STRING Out), T01 (timeout in seconds).
' RETURNS: %TRUE on success, %FALSE on failure.
' * * * SEALED * * *
FUNCTION NW_GetPeerInfo(BYVAL U01 AS DWORD, BYREF S01 AS STRING, BYVAL T01 AS LONG) COMMON AS LONG
    LOCAL E01 AS SOCKADDR_STORAGE
    LOCAL E02 AS LONG
    LOCAL E03 AS ASCIIZ * 46
    LOCAL E04 AS DWORD
    LOCAL E05 AS DOUBLE

    ' Initialize output
    S01 = ""

    ' Set timeout
    IF T01 <= 0 THEN T01 = %NW_TIMEOUT_DEFAULT / 1000
    E05 = TIMER + T01
    IF g_MCP_LogLevel > 1 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_GetPeerInfo: Timeout set to " & STR$(T01) & " seconds for socket " & STR$(U01))
    END IF

    ' Check for timeout
    IF TIMER > E05 THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_GetPeerInfo: Timeout on socket " & STR$(U01))
        END IF
        FUNCTION = %FALSE
        EXIT FUNCTION
    END IF

    ' Get peer address
    E02 = SIZEOF(E01)
    IF WS_GetPeerName(U01, BYVAL VARPTR(E01), E02) = 0 THEN
        E04 = 46 ' Buffer size for IPv4 (16) or IPv6 (46)
        IF WS_AddrToStringA(VARPTR(E01), E02, BYVAL %NULL, E03, E04) = 0 THEN
            S01 = E03
            IF LEN(S01) = 0 THEN
                IF g_MCP_LogLevel > 0 THEN
                    MCP_Log(%MCP_LOG_ERROR, "NW_GetPeerInfo: Failed to convert WS_AddrToStringA result to string on socket " & STR$(U01))
                END IF
                FUNCTION = %FALSE
                EXIT FUNCTION
            END IF
            IF g_MCP_LogLevel > 1 THEN
                MCP_Log(%MCP_LOG_DEBUG, "NW_GetPeerInfo: Retrieved IP " & S01 & " for socket " & STR$(U01) & ", family " & STR$(E01.ss_family))
            END IF
            FUNCTION = %TRUE
        ELSE
            IF g_MCP_LogLevel > 0 THEN
                LOCAL errMsg AS STRING
                SELECT CASE WSAGetLastError()
                    CASE %WSAEINVAL
                        errMsg = "Invalid arguments"
                    CASE %WSAEFAULT
                        errMsg = "Invalid address buffer"
                    CASE ELSE
                        errMsg = "WS_AddrToStringA failed"
                END SELECT
                MCP_Log(%MCP_LOG_ERROR, "NW_GetPeerInfo: " & errMsg & " on socket " & STR$(U01) & ", WSAError " & STR$(WSAGetLastError()))
            END IF
            FUNCTION = %FALSE
        END IF
    ELSE
        IF g_MCP_LogLevel > 0 THEN
            LOCAL errMsg2 AS STRING
            SELECT CASE WSAGetLastError()
                CASE %WSAEINVAL
                    errMsg2 = "Invalid arguments"
                CASE %WSAENOTCONN
                    errMsg2 = "Socket not connected"
                CASE %WSAEFAULT
                    errMsg2 = "Invalid address buffer"
                CASE ELSE
                    errMsg2 = "WS_GetPeerName failed"
                END SELECT
            MCP_Log(%MCP_LOG_ERROR, "NW_GetPeerInfo: " & errMsg2 & " on socket " & STR$(U01) & ", WSAError " & STR$(WSAGetLastError()))
        END IF
        FUNCTION = %FALSE
    END IF
END FUNCTION
'===============================================================================
' PURPOSE: Retrieves the IP address string and port number for a connected peer.
' UNICODE: The output IP string is ANSI/ASCII.
' PARAMS: U01 (socket handle), S01 (STRING Out for IP), N01 (LONG Out for port), T01 (timeout in seconds).
' RETURNS: %TRUE on success, %FALSE on failure.
' * * * SEALED * * *
#IF 1
FUNCTION NW_GetClientInfo(BYVAL U01 AS DWORD, BYREF S01 AS STRING, BYREF N01 AS LONG, BYVAL T01 AS LONG) COMMON AS LONG
    LOCAL E01 AS SOCKADDR_STORAGE
    LOCAL E02 AS LONG
    LOCAL E03 AS LONG
    LOCAL E04 AS DOUBLE
    S01 = ""
    N01 = 0
    IF T01 <= 0 THEN T01 = %NW_TIMEOUT_DEFAULT / 1000
    E04 = TIMER + T01
    IF g_MCP_LogLevel > 1 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_GetClientInfo: Timeout set to " & STR$(T01) & " seconds for socket " & STR$(U01))
    END IF
    IF TIMER > E04 THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_GetClientInfo: Timeout on socket " & STR$(U01))
        END IF
        FUNCTION = %FALSE
        EXIT FUNCTION
    END IF
    IF NW_GetPeerInfo(U01, S01, T01) = %TRUE THEN
        E02 = SIZEOF(E01)
        IF WS_GetPeerName(U01, BYVAL VARPTR(E01), E02) = 0 THEN
            IF E01.ss_family = %AF_INET THEN
                LOCAL E05 AS sockaddr_in PTR
                E05 = VARPTR(E01)
                N01 = WS_ntohs(@E05.sin_port)
            ELSEIF E01.ss_family = %AF_INET6 THEN
                LOCAL E06 AS SOCKADDR_IN6 PTR ' Use SOCKADDR_IN6 from ws2ipdef.inc
                E06 = VARPTR(E01)
                N01 = WS_ntohs(@E06.sin6_port)
            ELSE
                IF g_MCP_LogLevel > 0 THEN
                    MCP_Log(%MCP_LOG_ERROR, "NW_GetClientInfo: Unsupported address family " & STR$(E01.ss_family) & " on socket " & STR$(U01))
                END IF
                FUNCTION = %FALSE
                EXIT FUNCTION
            END IF
            IF g_MCP_LogLevel > 1 THEN
                MCP_Log(%MCP_LOG_DEBUG, "NW_GetClientInfo: Retrieved IP " & S01 & " and port " & STR$(N01) & " for socket " & STR$(U01) & ", family " & STR$(E01.ss_family))
            END IF
            FUNCTION = %TRUE
        ELSE
            E03 = WSAGetLastError()
            IF g_MCP_LogLevel > 0 THEN
                LOCAL errMsg AS STRING
                SELECT CASE E03
                    CASE %WSAEINVAL
                        errMsg = "Invalid arguments"
                    CASE %WSAENOTCONN
                        errMsg = "Socket not connected"
                    CASE %WSAEFAULT
                        errMsg = "Invalid address buffer"
                    CASE ELSE
                        errMsg = "WS_GetPeerName failed"
                END SELECT
                MCP_Log(%MCP_LOG_ERROR, "NW_GetClientInfo: " & errMsg & " on socket " & STR$(U01) & ", WSAError " & STR$(E03))
            END IF
            FUNCTION = %FALSE
        END IF
    ELSE
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_GetClientInfo: NW_GetPeerInfo failed on socket " & STR$(U01))
        END IF
        FUNCTION = %FALSE
    END IF
END FUNCTION
#ELSE
FUNCTION NW_GetClientInfo(BYVAL U01 AS DWORD, BYREF S01 AS STRING, BYREF N01 AS LONG, BYVAL T01 AS LONG) COMMON AS LONG
    LOCAL E01 AS SOCKADDR_STORAGE
    LOCAL E02 AS LONG
    LOCAL E03 AS LONG
    LOCAL E04 AS DOUBLE

    ' Initialize outputs
    S01 = ""
    N01 = 0

    ' Set timeout
    IF T01 <= 0 THEN T01 = %NW_TIMEOUT_DEFAULT / 1000
    E04 = TIMER + T01
    IF g_MCP_LogLevel > 1 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_GetClientInfo: Timeout set to " & STR$(T01) & " seconds for socket " & STR$(U01))
    END IF

    ' Check for timeout
    IF TIMER > E04 THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_GetClientInfo: Timeout on socket " & STR$(U01))
        END IF
        FUNCTION = %FALSE
        EXIT FUNCTION
    END IF

    ' Get IP address
    IF NW_GetPeerInfo(U01, S01, T01) = %TRUE THEN
        ' Get port
        E02 = SIZEOF(E01)
        IF WS_GetPeerName(U01, BYVAL VARPTR(E01), E02) = 0 THEN
            IF E01.ss_family = %AF_INET THEN
                LOCAL E05 AS sockaddr_in PTR
                E05 = VARPTR(E01)
                N01 = WS_ntohs(@E05.sin_port)
            ELSEIF E01.ss_family = %AF_INET6 THEN
                LOCAL E06 AS sockaddr_in6 PTR
                E06 = VARPTR(E01)
                N01 = WS_ntohs(@E06.sin6_port)
            ELSE
                IF g_MCP_LogLevel > 0 THEN
                    MCP_Log(%MCP_LOG_ERROR, "NW_GetClientInfo: Unsupported address family " & STR$(E01.ss_family) & " on socket " & STR$(U01))
                END IF
                FUNCTION = %FALSE
                EXIT FUNCTION
            END IF
            IF g_MCP_LogLevel > 1 THEN
                MCP_Log(%MCP_LOG_DEBUG, "NW_GetClientInfo: Retrieved IP " & S01 & " and port " & STR$(N01) & " for socket " & STR$(U01) & ", family " & STR$(E01.ss_family))
            END IF
            FUNCTION = %TRUE
        ELSE
            E03 = WSAGetLastError()
            IF g_MCP_LogLevel > 0 THEN
                LOCAL errMsg AS STRING
                SELECT CASE E03
                    CASE %WSAEINVAL
                        errMsg = "Invalid arguments"
                    CASE %WSAENOTCONN
                        errMsg = "Socket not connected"
                    CASE %WSAEFAULT
                        errMsg = "Invalid address buffer"
                    CASE ELSE
                        errMsg = "WS_GetPeerName failed"
                END SELECT
                MCP_Log(%MCP_LOG_ERROR, "NW_GetClientInfo: " & errMsg & " on socket " & STR$(U01) & ", WSAError " & STR$(E03))
            END IF
            FUNCTION = %FALSE
        END IF
    ELSE
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_GetClientInfo: NW_GetPeerInfo failed on socket " & STR$(U01))
        END IF
        FUNCTION = %FALSE
    END IF
END FUNCTION
#ENDIF
'===============================================================================
' PURPOSE: Monitors an array of sockets for readability using the select() model.
' UNICODE: Not applicable; deals with socket handles and status flags.
' PARAMS:  socketArray() (In), readStatus() (Out), timeoutSec (In).
' RETURNS: %TRUE if one or more sockets are ready, %FALSE on timeout or error.
'
FUNCTION NW_MonitorMultipleSockets(BYREF socketArray() AS DWORD, BYREF readStatus() AS LONG, BYVAL timeoutSec AS LONG) COMMON AS LONG
    LOCAL readSet AS fd_setstruc, tv AS timeval, i AS LONG, result AS LONG, errCode AS LONG
    FUNCTION = %FALSE  ' Default to failure/timeout
    IF UBOUND(socketArray) - LBOUND(socketArray) + 1 > %FD_SETSIZE THEN
        IF g_MCP_LogLevel > 0 THEN MCP_Log(%MCP_LOG_ERROR, "NW_MonitorMultipleSockets: Too many sockets, exceeds FD_SETSIZE")
        EXIT FUNCTION
    END IF
    FD_ZERO(readSet)
    FOR i = LBOUND(socketArray) TO UBOUND(socketArray)
        IF socketArray(i) <> %INVALID_SOCKET THEN FD_SET(socketArray(i), readSet)
    NEXT i
    tv.tv_sec = timeoutSec
    tv.tv_usec = 0
    REDIM readStatus(LBOUND(socketArray) TO UBOUND(socketArray))
    result = NW_WSASelect(0, readSet, BYVAL %NULL, BYVAL %NULL, tv)
    IF result > 0 THEN
        FOR i = LBOUND(socketArray) TO UBOUND(socketArray)
            readStatus(i) = IIF(socketArray(i) <> %INVALID_SOCKET AND FD_ISSET(socketArray(i), readSet), 1, 0)
        NEXT i
        FUNCTION = %TRUE
     ELSEIF result = %SOCKET_ERROR THEN
        errCode = WSAGetLastError()
        IF g_MCP_LogLevel > 0 THEN MCP_Log(%MCP_LOG_ERROR, "NW_MonitorMultipleSockets: select failed, WSAError " & STR$(errCode))
    ELSE  ' result = 0, timeout
        IF g_MCP_LogLevel > 1 THEN MCP_Log(%MCP_LOG_DEBUG, "NW_MonitorMultipleSockets: Timeout after " & STR$(timeoutSec) & " seconds")
    END IF
END FUNCTION
'--------------------------------------------------------------------------------------------------
' PURPOSE: Logs a socket event message, typically used with WSAAsyncSelect.
' UNICODE: The log message is a STRING, handled by MCP_Log (must support UTF-8/ASCII).
' PARAMS: U01 (socket handle), N01 (event type, e.g., %FD_READ), N02 (error code).
' RETURNS: None.
SUB NW_LogSocketEvent(BYVAL U01 AS DWORD, BYVAL N01 AS LONG, BYVAL N02 AS LONG)
    LOCAL S02 AS STRING

    ' Validate inputs
    IF U01 = %INVALID_SOCKET THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_LogSocketEvent: Invalid socket handle")
        END IF
        EXIT SUB
    END IF
    IF N02 < 0 OR N02 > %WSABASEERR + 10000 THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_DEBUG, "NW_LogSocketEvent: Suspicious error code " & STR$(N02) & " for socket " & STR$(U01))
        END IF
    END IF

    ' Map event type to description
    SELECT CASE N01
        CASE %FD_READ
            S02 = "Read event"
        CASE %FD_WRITE
            S02 = "Write event"
        CASE %FD_CLOSE
            S02 = "Close event"
        CASE %FD_ACCEPT
            S02 = "Accept event"
        CASE %FD_CONNECT
            S02 = "Connect event"
        CASE %FD_OOB
            S02 = "Out-of-band data event"
        CASE %FD_QOS
            S02 = "Quality of service event"
        CASE %FD_GROUP_QOS
            S02 = "Group quality of service event"
        CASE %FD_ROUTING_INTERFACE_CHANGE
            S02 = "Routing interface change event"
        CASE %FD_ADDRESS_LIST_CHANGE
            S02 = "Address list change event"
        CASE ELSE
            S02 = "Unknown event " & FORMAT$(N01)
            IF g_MCP_LogLevel > 0 THEN
                MCP_Log(%MCP_LOG_DEBUG, "NW_LogSocketEvent: Invalid event type " & STR$(N01) & " for socket " & STR$(U01))
            END IF
    END SELECT

    ' Check for non-ASCII characters in log message
    IF INSTR(S02, ANY CHR$(128 TO 255)) THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_DEBUG, "NW_LogSocketEvent: Non-ASCII characters detected in log message for socket " & STR$(U01))
        END IF
    END IF

    ' Log the event
    MCP_Log(IIF(N02 = 0, %MCP_LOG_INFO, %MCP_LOG_ERROR), _
        "NW_LogSocketEvent: s=" & FORMAT$(U01) & " " & S02 & _
        IIF$(N02 > 0, " WSA=" & FORMAT$(N02), ""))
END SUB

'===============================================================================
'
'===============================================================================
'DECLARE FUNCTION WS_Send LIB "Ws2_32.dll" ALIAS "send" (BYVAL s AS DWORD, BYVAL buf AS DWORD, BYVAL nLen AS LONG, BYVAL flags AS LONG) AS LONG

' PURPOSE: Sends a message to every valid socket in a given array.
' UNICODE: Byte-stream safe. The message STRING must be properly encoded (e.g., UTF-8 or ASCII).
' PARAMS: A01() (socket array), S01 (STRING message), T01 (timeout in seconds).
' RETURNS: %TRUE if any bytes were sent successfully, %FALSE otherwise.
FUNCTION NW_BroadcastMessage(BYREF A01() AS DWORD, BYREF S01 AS STRING, BYVAL T01 AS LONG) COMMON AS LONG
    LOCAL N01 AS LONG ' Bytes sent to current socket
    LOCAL N02 AS LONG ' Total bytes sent
    LOCAL N03 AS LONG ' Loop index
    LOCAL E01 AS LONG ' Error code

    ' Initialize
    N02 = 0

    ' Validate input
    IF LEN(S01) = 0 THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_BroadcastMessage: Empty message")
        END IF
        FUNCTION = %FALSE
        EXIT FUNCTION
    END IF
    ' Check for non-ASCII characters in message (optional, if ASCII-only protocol)
    IF INSTR(S01, ANY CHR$(128 TO 255)) THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_DEBUG, "NW_BroadcastMessage: Non-ASCII characters detected in message")
        END IF
    END IF

    ' Set timeout
    IF T01 <= 0 THEN T01 = %NW_TIMEOUT_DEFAULT / 1000
    IF g_MCP_LogLevel > 1 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_BroadcastMessage: Timeout set to " & STR$(T01) & " seconds for broadcasting message of " & STR$(LEN(S01)) & " bytes")
    END IF

    ' Send message to all valid sockets
    FOR N03 = LBOUND(A01) TO UBOUND(A01)
        IF A01(N03) <> %INVALID_SOCKET THEN
            N01 = NW_Send(A01(N03), S01, T01)
            IF N01 = %NW_SOCKET_ERROR THEN
                E01 = WSAGetLastError()
                IF g_MCP_LogLevel > 0 THEN
                    LOCAL errMsg AS STRING
                    SELECT CASE E01
                        CASE %WSAEWOULDBLOCK
                            errMsg = "Non-blocking operation would block"
                        CASE %WSAENOTCONN
                            errMsg = "Socket not connected"
                        CASE %WSAECONNRESET
                            errMsg = "Connection reset by peer"
                        CASE ELSE
                            errMsg = "NW_Send failed"
                    END SELECT
                    MCP_Log(%MCP_LOG_ERROR, "NW_BroadcastMessage: " & errMsg & " on socket " & STR$(A01(N03)) & ", WSAError " & STR$(E01))
                END IF
            ELSE
                N02 = N02 + N01
                IF g_MCP_LogLevel > 1 THEN
                    MCP_Log(%MCP_LOG_DEBUG, "NW_BroadcastMessage: Sent " & STR$(N01) & " bytes to socket " & STR$(A01(N03)))
                END IF
            END IF
        END IF
    NEXT

    IF g_MCP_LogLevel > 1 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_BroadcastMessage: Total " & STR$(N02) & " bytes sent across all sockets")
    END IF
    FUNCTION = IIF&(N02 > 0, %TRUE, %FALSE)
END FUNCTION
'===============================================================================
' PURPOSE: Reads a single CRLF or LF-terminated line of text from a socket.
' UNICODE: Byte-stream safe. Returns a STRING of raw bytes for the caller to decode.
' PARAMS: U01 (socket handle), S01 (receives the line of text), T01 (timeout in seconds).
' RETURNS: %TRUE on success (line received), %FALSE on disconnect, error, or timeout.
' NOTE: This implementation reads byte-by-byte to ensure it NEVER reads past the EOL marker,
'       preventing data theft from subsequent socket receive calls. It does NOT use the threaded buffer.
FUNCTION NW_LineInput(BYVAL U01 AS DWORD, BYREF S01 AS STRING, BYVAL T01 AS LONG) COMMON AS LONG
    LOCAL B01 AS BYTE      ' Single byte buffer
    LOCAL R01 AS LONG      ' Return value (%TRUE/%FALSE)
    LOCAL E01 AS DOUBLE    ' Timeout marker
    LOCAL E02 AS LONG      ' WSA Error Code
    LOCAL N01 AS LONG      ' Result of WS_Recv
    LOCAL N02 AS LONG      ' Max line length (safety break)

    S01 = ""
    R01 = %FALSE
    N02 = 65536 ' 64KB max line length safety break

    ' Set absolute timeout
    IF T01 <= 0 THEN T01 = %NW_TIMEOUT_DEFAULT / 1000
    E01 = TIMER + T01

    DO
        ' 1. Check master timeout
        IF TIMER > E01 THEN
            IF g_MCP_LogLevel > 0 THEN
                MCP_Log(%MCP_LOG_ERROR, "NW_LineInput: Timeout reading line on socket " & STR$(U01))
            END IF
            GOTO enx ' Fail (R01 = %FALSE)
        END IF

        ' 2. Attempt to read one single byte
        N01 = WS_Recv(U01, VARPTR(B01), 1, 0)

        IF N01 = 1 THEN
            ' 3. Got one byte
            IF B01 = %LF THEN ' Found Line Feed (End of line)
                R01 = %TRUE ' Success
                EXIT DO
            ELSEIF B01 <> %CR THEN ' Ignore Carriage Return, append others
                S01 &= CHR$(B01)
            END IF

        ELSEIF N01 = %SOCKET_ERROR THEN
            ' 4. Socket Error
            E02 = WSAGetLastError()
            IF E02 = %WSAEWOULDBLOCK THEN
                SLEEP 1 ' Socket buffer is empty, yield CPU and loop again
                ITERATE DO
            ELSE
                ' 5. Fatal Error (e.g., Connection Reset)
                IF g_MCP_LogLevel > 0 THEN
                    MCP_Log(%MCP_LOG_ERROR, "NW_LineInput: WS_Recv failed, WSAError " & STR$(E02) & " on socket " & STR$(U01))
                END IF
                GOTO enx ' Fail (R01 = %FALSE)
            END IF

        ELSEIF N01 = 0 THEN
            ' 6. Graceful Disconnect (EOF)
            IF g_MCP_LogLevel > 1 THEN
                MCP_Log(%MCP_LOG_DEBUG, "NW_LineInput: Graceful disconnect on socket " & STR$(U01))
            END IF
            IF LEN(S01) > 0 THEN R01 = %TRUE ' Return partial line if we have one
            GOTO enx
        END IF

        ' 7. Safety break (Line too long)
        IF LEN(S01) > N02 THEN
            IF g_MCP_LogLevel > 0 THEN
                MCP_Log(%MCP_LOG_ERROR, "NW_LineInput: Line buffer overflow (> 64KB) on socket " & STR$(U01))
            END IF
            GOTO enx ' Fail (R01 = %FALSE)
        END IF
    LOOP

enx:
    FUNCTION = R01
END FUNCTION
'===============================================================================
'
'===============================================================================
' PURPOSE: Sends a Unicode WSTRING line with UTF-16LE CRLF (0D 00 0A 00).
' UNICODE: Fully Unicode safe. Handles WSTRING data directly.
' PARAMS: U01 (LONG In) - Socket handle, U02 (WSTRING In) - Text to send, T01 (timeout in seconds).
' RETURNS: Number of bytes sent, or %NW_SOCKET_ERROR on failure.
' * * * SEALED * * *
FUNCTION NW_PrintW(BYVAL U01 AS LONG, BYREF U02 AS WSTRING, BYVAL T01 AS LONG) COMMON AS LONG
    LOCAL L01 AS WSTRING ' Wide buffer (data + CRLF)
    LOCAL B01 AS STRING  ' Byte buffer (for sending)
    LOCAL N01 AS LONG  ' Send result
    LOCAL E01 AS LONG  ' Error code
'   LOCAL N02 AS LONG  ' (No longer needed, handled by utility function)

    IF LEN(U02) = 0 THEN
        IF g_MCP_LogLevel > 1 THEN
            MCP_Log(%MCP_LOG_DEBUG, "NW_PrintW: Empty WSTRING on socket " & STR$(U01))
        END IF
        FUNCTION = 0
        EXIT FUNCTION
    END IF

    ' Append the UTF-16LE CRLF (Wide CHAR 13, Wide CHAR 10)
    L01 = U02 & $$CRLF

    ' --- REFACTORED LOGIC: Use shared utility ---
    ' Convert WSTRING to raw byte STRING using the shared utility function
    B01 = INT_WstrToByteString(L01)
    ' --- END REFACTOR ---

    ' Send the raw byte buffer
    N01 = NW_Send(U01, B01, T01)

    IF N01 = LEN(B01) THEN
        IF g_MCP_LogLevel > 1 THEN
            MCP_Log(%MCP_LOG_DEBUG, "NW_PrintW: Successfully sent " & STR$(N01) & " bytes (UTF-16LE) on socket " & STR$(U01))
        END IF
        FUNCTION = N01
    ELSE
        E01 = WSAGetLastError()
        IF g_MCP_LogLevel > 0 THEN
            LOCAL S01 AS STRING
            SELECT CASE E01
                CASE %WSAEWOULDBLOCK
                    S01 = "Non-blocking operation would block"
                CASE %WSAENOTCONN
                    S01 = "Socket not connected"
                CASE %WSAECONNRESET
                    S01 = "Connection reset by peer"
                CASE ELSE
                    S01 = "NW_Send failed, WSAError " & STR$(E01)
            END SELECT
            MCP_Log(%MCP_LOG_ERROR, "NW_PrintW: " & S01 & " on socket " & STR$(U01))
        END IF
        FUNCTION = %NW_SOCKET_ERROR
    END IF
END FUNCTION
'===============================================================================


' ===============================================================================
' PURPOSE: Reads a UTF-16LE line from a socket, terminated by CRLF (0D 00 0A 00).
' UNICODE: Returns a WSTRING with the received line.
' PARAMS: U01 (socket handle), U02 (WSTRING Out), T01 (timeout in seconds).
' RETURNS: >0 (chars), 0 (disconnect), %NW_SOCKET_ERROR (error/timeout).
' * * * SEALED * * *
FUNCTION NW_LineInputW(BYVAL U01 AS LONG, BYREF U02 AS WSTRING, BYVAL T01 AS LONG) COMMON AS LONG

    LOCAL S_WCRLF AS STRING
    LOCAL D01 AS DOUBLE
    LOCAL N01 AS LONG
    LOCAL N02 AS LONG
    LOCAL L01 AS STRING
    LOCAL R01 AS LONG

    ' Initialize
    U02 = ""
    R01 = %NW_SOCKET_ERROR
    S_WCRLF = CHR$(&H0D, &H00, &H0A, &H00)
    IF T01 <= 0 THEN T01 = %NW_TIMEOUT_DEFAULT / 1000
    D01 = TIMER + T01

    ' If this thread is now using a different socket, clear this thread's private buffer.
    IF s_Socket <> U01 THEN
        s_Buffer = ""
        s_Socket = U01
    END IF

    DO
        ' 1. Check this thread's private buffer first
        N02 = INSTR(s_Buffer, S_WCRLF)
        IF N02 > 0 THEN
            L01 = LEFT$(s_Buffer, N02 - 1)
            s_Buffer = MID$(s_Buffer, N02 + 4) ' Remove line and CRLF

            IF (LEN(L01) MOD 2) <> 0 AND g_MCP_LogLevel > 0 THEN
                MCP_Log(%MCP_LOG_WARN, "NW_LineInputW: Odd byte count (" & STR$(LEN(L01)) & ") found before EOL. Data may be corrupt.")
            END IF

            U02 = STRING$(LEN(L01) / 2, 0)
            MoveMemory(STRPTR(U02), STRPTR(L01), LEN(L01))
            R01 = LEN(U02)
            GOTO enx
        END IF

        IF TIMER > D01 THEN
            IF g_MCP_LogLevel > 0 THEN MCP_Log(%MCP_LOG_ERROR, "NW_LineInputW: Master timeout on socket " & STR$(U01))
            GOTO enx
        END IF

        ' 3. Buffer has partial/no data, read more from socket
        L01 = SPACE$(4096)
        N01 = NW_Recv(U01, L01, 4096, 0) ' Use a small timeout, master loop handles the rest

        SELECT CASE N01
            CASE IS > 0
                ' Data received, append to this thread's private buffer
                s_Buffer = s_Buffer & L01
            CASE 0 ' Graceful disconnect
                R01 = 0
                GOTO enx
            CASE %NW_WOULDBLOCK
                SLEEP 10
                 ITERATE LOOP
            CASE %NW_SOCKET_ERROR ' Fatal error
                GOTO enx
        END SELECT
    LOOP

enx:
    ' If connection is closed or errored, clear the buffer for the next call on this thread
    IF R01 <= 0 THEN
       s_Buffer = ""
       s_Socket = 0
    END IF
    FUNCTION = R01
END FUNCTION
'===============================================================================
' PURPOSE: Wrapper for Winsock select() function.
' PARAMS: nfds (ignored on Windows), readfds, writefds, exceptfds, timeout.
' RETURNS: Number of sockets ready, 0 on timeout, %SOCKET_ERROR on error.
FUNCTION NW_WSASelect(BYVAL nfds AS LONG, _
                      BYREF readfds AS fd_setstruc, _
                      BYREF writefds AS fd_setstruc, _
                      BYREF exceptfds AS fd_setstruc, _
                      BYREF TM AS timeval) COMMON AS LONG
                      G_REG
    R01=WS_WSASELECT(nfds, readfds, writefds, exceptfds, TM)
    FUNCTION = R01
END FUNCTION

'===============================================================================
' FUNCTION: NW_CreateTestFile
' PURPOSE: Creates a test file filled with 'X' characters for benchmarking or demos.
' PARAMS: filePath (STRING In), fileSize (QUAD In).
' RETURNS: %TRUE on success, %FALSE on failure.
'===============================================================================
FUNCTION NW_CreateTestFile(BYVAL filePath AS STRING, BYVAL fileSize AS QUAD) COMMON AS LONG
    LOCAL ff AS LONG
    LOCAL chunk AS STRING
    LOCAL i AS QUAD
    LOCAL remaining AS QUAD
    LOCAL result AS LONG
    G_S01

    result = %FALSE
    chunk = STRING$(65536, "X") ' 64KB chunk

    ff = FREEFILE
    OPEN filePath FOR BINARY AS ff
    IF ERR THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_CreateTestFile: Failed to create file '" & filePath & "', Error " & STR$(ERR))
        END IF
        GOTO cleanup
    END IF

    i = 0
    WHILE i < fileSize
        remaining = fileSize - i
        IF remaining >= 65536 THEN
            PUT ff, , chunk
            i = i + 65536
        ELSE
            S01=LEFT$(chunk, remaining)
            PUT ff, , S01
            i = i + remaining
        END IF
    WEND

    result = %TRUE
    IF g_MCP_LogLevel > 1 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_CreateTestFile: Successfully created file '" & filePath & "' of size " & STR$(fileSize) & " bytes.")
    END IF

cleanup:
    IF ff THEN CLOSE ff
    FUNCTION = result
END FUNCTION
'===============================================================================
' SUB: NW_PauseForUser
' PURPOSE: Pauses execution and waits for user input (console utility).
' PARAMS: message (STRING In) - Message to display.
' RETURNS: None.
'===============================================================================
SUB NW_PauseForUser(BYVAL message AS STRING) COMMON
    X_AU message
    X_AU "Press any key to continue..."
    ? "" ' Wait for keypress
END SUB
'===============================================================================
' FUNCTION: NW_WaitForThread
' PURPOSE: Waits for a thread to finish with a timeout, retrieving its result.
' PARAMS: threadHandle (DWORD In), timeoutSec (LONG In), result (LONG Out).
' RETURNS: %TRUE if thread finished successfully, %FALSE on timeout or error.
'===============================================================================
FUNCTION NW_WaitForThread(BYVAL threadHandle AS DWORD, BYVAL timeoutSec AS LONG, BYREF result AS LONG) COMMON AS LONG
    LOCAL waitResult AS LONG
    LOCAL apiResult AS LONG
    result = %NW_SOCKET_ERROR
    IF threadHandle = 0 THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_WaitForThread: Invalid thread handle.")
        END IF
        FUNCTION = %FALSE
        EXIT FUNCTION
    END IF

    waitResult = WaitForSingleObject(threadHandle, timeoutSec * 1000)
    SELECT CASE waitResult
        CASE %WAIT_OBJECT_0
            IF GetExitCodeThread(threadHandle, result) THEN
                IF result = &H103& THEN ' STILL_ACTIVE (shouldn't happen after WAIT_OBJECT_0, but just in case)
                    result = %NW_SOCKET_ERROR
                    FUNCTION = %FALSE
                ELSE
                    IF g_MCP_LogLevel > 1 THEN
                        MCP_Log(%MCP_LOG_DEBUG, "NW_WaitForThread: Thread finished with result " & STR$(result))
                    END IF
                    THREAD CLOSE threadHandle TO apiResult
                    FUNCTION = %TRUE
                END IF
            ELSE
                apiResult = GetLastError()
                IF g_MCP_LogLevel > 0 THEN
                    MCP_Log(%MCP_LOG_ERROR, "NW_WaitForThread: GetExitCodeThread failed, Error " & STR$(apiResult))
                END IF
                THREAD CLOSE threadHandle TO apiResult
                FUNCTION = %FALSE
            END IF
        CASE %WAIT_TIMEOUT
            IF g_MCP_LogLevel > 0 THEN
                MCP_Log(%MCP_LOG_ERROR, "NW_WaitForThread: Timeout waiting for thread.")
            END IF
            FUNCTION = %FALSE
        CASE ELSE
            apiResult = GetLastError()
            IF g_MCP_LogLevel > 0 THEN
                MCP_Log(%MCP_LOG_ERROR, "NW_WaitForThread: WaitForSingleObject failed, Error " & STR$(apiResult))
            END IF
            FUNCTION = %FALSE
    END SELECT
END FUNCTION
'===============================================================================
' FUNCTION: NW_ResolveAndValidateHost
' PURPOSE: Resolves a hostname and validates the result (non-empty, valid IP).
' PARAMS: hostname (STRING In), ipAddress (STRING Out), timeoutSec (LONG In).
' RETURNS: %TRUE if resolved and validated, %FALSE otherwise.
'===============================================================================
FUNCTION NW_ResolveAndValidateHost(BYVAL hostname AS STRING, BYREF ipAddress AS STRING, BYVAL timeoutSec AS LONG) COMMON AS LONG
    LOCAL result AS LONG

    ipAddress = ""
    result = NW_ResolveHost(hostname, ipAddress, timeoutSec)

    IF result = %TRUE AND LEN(ipAddress) > 0 THEN
        ' Basic validation: Check if it's a plausible IP (contains a dot or colon)
        IF INSTR(ipAddress, ".") > 0 OR INSTR(ipAddress, ":") > 0 THEN
            IF g_MCP_LogLevel > 1 THEN
                MCP_Log(%MCP_LOG_DEBUG, "NW_ResolveAndValidateHost: Validated IP '" & ipAddress & "' for host '" & hostname & "'")
            END IF
            FUNCTION = %TRUE
            EXIT FUNCTION
        END IF
    END IF

    IF g_MCP_LogLevel > 0 THEN
        MCP_Log(%MCP_LOG_ERROR, "NW_ResolveAndValidateHost: Failed to resolve or validate host '" & hostname & "'")
    END IF
    FUNCTION = %FALSE
END FUNCTION
'===============================================================================
' FUNCTION: NW_PerformHttpTransaction
' PURPOSE: Performs a complete HTTP transaction (client-side).
' PARAMS: method, url, requestBody, responseHeaders, responseBody, timeoutSec.
' RETURNS: HTTP status code on success, %NW_SOCKET_ERROR on failure.
'===============================================================================
FUNCTION NW_PerformHttpTransaction(BYVAL U01 AS STRING, BYVAL url AS STRING, BYVAL requestBody AS STRING, BYREF responseHeaders AS STRING, BYREF responseBody AS STRING, BYVAL timeoutSec AS LONG) COMMON AS LONG
    LOCAL T01 AS DWORD
    LOCAL S01 AS STRING
    LOCAL T02 AS LONG
    LOCAL PATH AS STRING
    LOCAL request AS STRING
    LOCAL S02 AS STRING
    LOCAL contentLength AS LONG
    LOCAL statusCode AS LONG
    LOCAL result AS LONG

    responseHeaders = ""
    responseBody = ""
    statusCode = %NW_SOCKET_ERROR

    ' Parse URL (simplified: assumes http://host:port/path)
    IF LEFT$(UCASE$(url), 7) <> "HTTP://" THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_PerformHttpTransaction: Only HTTP URLs are supported.")
        END IF
        FUNCTION = %NW_SOCKET_ERROR
        EXIT FUNCTION
    END IF

    url = MID$(url, 8) ' Remove "http://"
    T02 = 80
    IF INSTR(url, ":") > 0 THEN
        S01 = LEFT$(url, INSTR(url, ":") - 1)
        url = MID$(url, INSTR(url, ":") + 1)
        IF INSTR(url, "/") > 0 THEN
            T02 = VAL(LEFT$(url, INSTR(url, "/") - 1))
            PATH = MID$(url, INSTR(url, "/"))
        ELSE
            T02 = VAL(url)
            PATH = "/"
        END IF
    ELSEIF INSTR(url, "/") > 0 THEN
        S01 = LEFT$(url, INSTR(url, "/") - 1)
        PATH = MID$(url, INSTR(url, "/"))
    ELSE
        S01 = url
        PATH = "/"
    END IF

    ' Connect to server
    T01 = NW_Connect(S01, T02, timeoutSec)
    IF T01 = %INVALID_SOCKET THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_PerformHttpTransaction: Failed to connect to " & S01 & ":" & STR$(T02))
        END IF
        FUNCTION = %NW_SOCKET_ERROR
        EXIT FUNCTION
    END IF

    ' Build and send HTTP request
    request = U01 & " " & PATH & " HTTP/1.1" & $CRLF
    request = request & "Host: " & S01 & $CRLF
    request = request & "Connection: close" & $CRLF
    IF LEN(requestBody) > 0 THEN
        request = request & "Content-Length: " & STR$(LEN(requestBody)) & $CRLF
    END IF
    request = request & $CRLF
    IF LEN(requestBody) > 0 THEN
        request = request & requestBody
    END IF

    result = NW_Send(T01, request, timeoutSec)
    IF result = %NW_SOCKET_ERROR THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_PerformHttpTransaction: Failed to send HTTP request.")
        END IF
        NW_CloseSocket(T01)
        FUNCTION = %NW_SOCKET_ERROR
        EXIT FUNCTION
    END IF

    ' Read response headers
    DO
        IF NW_LineInput(T01, S02, timeoutSec) = %FALSE THEN
            IF g_MCP_LogLevel > 0 THEN
                MCP_Log(%MCP_LOG_ERROR, "NW_PerformHttpTransaction: Failed to read HTTP response headers.")
            END IF
            NW_CloseSocket(T01)
            FUNCTION = %NW_SOCKET_ERROR
            EXIT FUNCTION
        END IF
        responseHeaders = responseHeaders & S02 & $CRLF
        IF S02 = "" THEN EXIT DO ' End of headers
        ' Parse status line
        IF LEFT$(S02, 4) = "HTTP" THEN
            statusCode = VAL(MID$(S02, INSTR(S02, " ") + 1))
        END IF
        ' Look for Content-Length
        IF LEFT$(UCASE$(S02), 14) = "CONTENT-LENGTH" THEN
            contentLength = VAL(MID$(S02, INSTR(S02, ":") + 1))
        END IF
    LOOP

    ' Read response body
    IF contentLength > 0 THEN
        responseBody = SPACE$(contentLength)
        result = NW_Recv(T01, responseBody, contentLength, timeoutSec)
        IF result <> contentLength THEN
            IF g_MCP_LogLevel > 0 THEN
                MCP_Log(%MCP_LOG_ERROR, "NW_PerformHttpTransaction: Failed to read full HTTP response body.")
            END IF
            NW_CloseSocket(T01)
            FUNCTION = %NW_SOCKET_ERROR
            EXIT FUNCTION
        END IF
    ELSE
        ' If no Content-Length, read until connection closes (simplified)
        DO
            result = NW_Recv(T01, S02, 4096, timeoutSec)
            IF result > 0 THEN
                responseBody = responseBody & LEFT$(S02, result)
            ELSEIF result = 0 THEN
                EXIT DO ' Graceful disconnect
            ELSE
                IF g_MCP_LogLevel > 0 THEN
                    MCP_Log(%MCP_LOG_ERROR, "NW_PerformHttpTransaction: Error reading HTTP response body.")
                END IF
                NW_CloseSocket(T01)
                FUNCTION = %NW_SOCKET_ERROR
                EXIT FUNCTION
            END IF
        LOOP
    END IF

    NW_CloseSocket(T01)
    IF g_MCP_LogLevel > 1 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_PerformHttpTransaction: Completed request to " & url & ", Status: " & STR$(statusCode))
    END IF
    FUNCTION = statusCode
END FUNCTION
'===============================================================================
' FUNCTION: NW_TestConnection
' PURPOSE: Tests if a server is reachable by attempting to connect.
' PARAMS: serverIP (STRING In), serverPort (LONG In), timeoutSecs (LONG In).
' RETURNS: %TRUE if connection succeeds, %FALSE otherwise.
'===============================================================================
FUNCTION NW_TestConnection(BYVAL serverIP AS STRING, BYVAL serverPort AS LONG, BYVAL timeoutSecs AS LONG) COMMON AS LONG
    LOCAL T01 AS DWORD

    T01 = NW_Connect(serverIP, serverPort, timeoutSecs)
    IF T01 <> %INVALID_SOCKET THEN
        NW_CloseSocket(T01)
        IF g_MCP_LogLevel > 1 THEN
            MCP_Log(%MCP_LOG_DEBUG, "NW_TestConnection: Successfully connected to " & serverIP & ":" & STR$(serverPort))
        END IF
        FUNCTION = %TRUE
    ELSE
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_TestConnection: Failed to connect to " & serverIP & ":" & STR$(serverPort))
        END IF
        FUNCTION = %FALSE
    END IF
END FUNCTION
'===============================================================================
' FUNCTION: NW_StringToAddrAndConnect
' PURPOSE: Connects to a server using an "IP:Port" string.
' PARAMS: ipPort (STRING In), timeoutSecs (LONG In).
' RETURNS: Connected socket handle or %INVALID_SOCKET.
'===============================================================================
FUNCTION NW_StringToAddrAndConnect(BYVAL ipPort AS STRING, BYVAL timeoutSecs AS LONG) COMMON AS DWORD
    LOCAL addrLen AS LONG
    LOCAL T01 AS DWORD
    LOCAL S01 AS STRING
    LOCAL portStr AS STRING
    LOCAL T02 AS LONG
    LOCAL colonPos AS LONG

    ' Parse "IP:Port" string
    colonPos = INSTR(-1, ipPort, ":") ' Find last colon (for IPv6)
    IF colonPos = 0 THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_StringToAddrAndConnect: Invalid IP:Port format '" & ipPort & "'")
        END IF
        FUNCTION = %INVALID_SOCKET
        EXIT FUNCTION
    END IF

    S01 = LEFT$(ipPort, colonPos - 1)
    portStr = MID$(ipPort, colonPos + 1)
    T02 = VAL(portStr)

    IF T02 <= 0 OR T02 > 65535 THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_StringToAddrAndConnect: Invalid port '" & portStr & "' in '" & ipPort & "'")
        END IF
        FUNCTION = %INVALID_SOCKET
        EXIT FUNCTION
    END IF

    ' Connect using hostname and port
    T01 = NW_Connect(S01, T02, timeoutSecs)
    IF T01 = %INVALID_SOCKET THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_StringToAddrAndConnect: Failed to connect to '" & ipPort & "'")
        END IF
    ELSE
        IF g_MCP_LogLevel > 1 THEN
            MCP_Log(%MCP_LOG_DEBUG, "NW_StringToAddrAndConnect: Connected to '" & ipPort & "'")
        END IF
    END IF

    FUNCTION = T01
END FUNCTION
'===============================================================================
' FUNCTION: NW_AcceptClientInfo
' PURPOSE: Accepts a connection and directly returns client IP and port.
' PARAMS: serverSocket (DWORD In), timeoutSecs (LONG In), clientIP (STRING Out), clientPort (LONG Out).
' RETURNS: Accepted client socket or %INVALID_SOCKET.
'===============================================================================
FUNCTION NW_AcceptClientInfo(BYVAL serverSocket AS DWORD, BYVAL timeoutSecs AS LONG, BYREF clientIP AS STRING, BYREF clientPort AS LONG) COMMON AS DWORD
    LOCAL clientSocket AS DWORD

    clientIP = ""
    clientPort = 0
    clientSocket = NW_Accept(serverSocket, clientIP, clientPort, timeoutSecs)

    IF clientSocket <> %INVALID_SOCKET THEN
        IF g_MCP_LogLevel > 1 THEN
            MCP_Log(%MCP_LOG_DEBUG, "NW_AcceptClientInfo: Accepted connection from " & clientIP & ":" & STR$(clientPort))
        END IF
    ELSE
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_AcceptClientInfo: Failed to accept connection on socket " & STR$(serverSocket))
        END IF
    END IF

    FUNCTION = clientSocket
END FUNCTION
'===============================================================================
' FUNCTION: NW_SendFileSimple
' PURPOSE: Simplified interface to send a file over a socket.
' PARAMS: socket (DWORD In), filePath (STRING In), timeoutSecs (LONG In).
' RETURNS: Total bytes sent or %NW_SOCKET_ERROR.
'===============================================================================
FUNCTION NW_SendFileSimple(BYVAL U01 AS DWORD, BYVAL filePath AS STRING, BYVAL timeoutSecs AS LONG) COMMON AS LONG
    LOCAL result AS LONG

    result = NW_SendFile(U01, filePath, timeoutSecs)
    IF result = %NW_SOCKET_ERROR THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_SendFileSimple: Failed to send file '" & filePath & "' on socket " & STR$(U01))
        END IF
    ELSE
        IF g_MCP_LogLevel > 1 THEN
            MCP_Log(%MCP_LOG_DEBUG, "NW_SendFileSimple: Successfully sent " & STR$(result) & " bytes from file '" & filePath & "'")
        END IF
    END IF

    FUNCTION = result
END FUNCTION
'===============================================================================
'===============================================================================
' FUNCTION: NW_RecvFileSimple
' PURPOSE: Simplified interface to receive data into a file.
' PARAMS: socket (DWORD In), filePath (STRING In), expectedSize (LONG In), timeoutSecs (LONG In).
' RETURNS: Total bytes received or %NW_SOCKET_ERROR.
'===============================================================================
FUNCTION NW_RecvFileSimple(BYVAL U01 AS DWORD, BYVAL filePath AS STRING, BYVAL expectedSize AS LONG, BYVAL timeoutSecs AS LONG) COMMON AS LONG
    LOCAL result AS LONG

    ' Pass filePath as S01, expectedSize as N01 for NW_RecvStream
    result = NW_RecvStream(U01, filePath, expectedSize, timeoutSecs)
    IF result = %NW_SOCKET_ERROR THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_RecvFileSimple: Failed to receive file '" & filePath & "' on socket " & STR$(U01))
        END IF
    ELSE
        IF g_MCP_LogLevel > 1 THEN
            MCP_Log(%MCP_LOG_DEBUG, "NW_RecvFileSimple: Successfully received " & STR$(result) & " bytes into file '" & filePath & "'")
        END IF
    END IF

    FUNCTION = result
END FUNCTION
'===============================================================================
' FUNCTION: NW_SimpleHttpGet
' PURPOSE: Performs a simple HTTP GET request to a given URL.
' UNICODE: Byte-stream safe. Assumes URL is ASCII/UTF-8.
' PARAMS:
'   S01 (STRING In) - The full URL (e.g., "http://example.com/path").
'   S02 (STRING Out) - The HTTP response headers.
'   S03 (STRING Out) - The HTTP response body.
'   N01 (LONG In) - Timeout for the entire operation in seconds.
' RETURNS: HTTP status code (e.g., 200) on success, %NW_SOCKET_ERROR on failure.
'===============================================================================
FUNCTION NW_SimpleHttpGet(BYVAL S01 AS STRING, BYREF S02 AS STRING, BYREF S03 AS STRING, BYVAL N01 AS LONG) COMMON AS LONG
    LOCAL S04 AS STRING ' Parsed URL (without scheme)
    LOCAL S05 AS STRING ' Hostname
    LOCAL S06 AS STRING ' Path
    LOCAL U01 AS DWORD  ' Port
    LOCAL U02 AS DWORD  ' Socket
    LOCAL S07 AS STRING ' Request string
    LOCAL S08 AS STRING ' Temporary line buffer
    LOCAL U03 AS DWORD  ' Content-Length
    LOCAL N02 AS LONG   ' HTTP Status Code
    LOCAL N03 AS LONG   ' Result of send/recv operations
    LOCAL N04 AS LONG   ' Colon position in host:port
    LOCAL N05 AS LONG   ' Slash position in URL
    LOCAL N06 AS LONG   ' Dot position in status line

    ' Initialize outputs
    S02 = ""
    S03 = ""
    N02 = %NW_SOCKET_ERROR

    ' Validate URL
    IF LEN(S01) = 0 THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_SimpleHttpGet: Empty URL provided.")
        END IF
        FUNCTION = %NW_SOCKET_ERROR
        EXIT FUNCTION
    END IF

    ' --- PARSE URL ---
    ' Check for "http://"
    IF LEFT$(UCASE$(S01), 7) <> "HTTP://" THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_SimpleHttpGet: Only HTTP URLs are supported.")
        END IF
        FUNCTION = %NW_SOCKET_ERROR
        EXIT FUNCTION
    END IF
    S04 = MID$(S01, 8) ' Remove "http://"

    ' Find port (if specified) and path
    U01 = 80 ' Default HTTP port
    S06 = "/" ' Default path

    ' Look for the first '/' to separate host:port from path
    N05 = INSTR(S04, "/")
    IF N05 > 0 THEN
        S05 = LEFT$(S04, N05 - 1)
        S06 = MID$(S04, N05)
    ELSE
        S05 = S04
    END IF

    ' Look for ':' in the host part to extract port
    N04 = INSTR(S05, ":")
    IF N04 > 0 THEN
        U01 = VAL(MID$(S05, N04 + 1))
        IF U01 <= 0 OR U01 > 65535 THEN
            IF g_MCP_LogLevel > 0 THEN
                MCP_Log(%MCP_LOG_ERROR, "NW_SimpleHttpGet: Invalid port '" & MID$(S05, N04 + 1) & "' in URL '" & S01 & "'")
            END IF
            FUNCTION = %NW_SOCKET_ERROR
            EXIT FUNCTION
        END IF
        S05 = LEFT$(S05, N04 - 1)
    END IF

    IF g_MCP_LogLevel > 1 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_SimpleHttpGet: Parsed URL - Host: '" & S05 & "', Port: " & STR$(U01) & ", Path: '" & S06 & "'")
    END IF

    ' --- CONNECT TO SERVER ---
    U02 = NW_Connect(S05, U01, N01)
    IF U02 = %INVALID_SOCKET THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_SimpleHttpGet: Failed to connect to " & S05 & ":" & STR$(U01))
        END IF
        FUNCTION = %NW_SOCKET_ERROR
        EXIT FUNCTION
    END IF

    ' --- BUILD AND SEND HTTP REQUEST ---
    S07 = "GET " & S06 & " HTTP/1.1" & $CRLF
    S07 = S07 & "Host: " & S05 & $CRLF
    S07 = S07 & "Connection: close" & $CRLF ' Ask server to close connection
    S07 = S07 & "User-Agent: NW_Library/1.0" & $CRLF
    S07 = S07 & $CRLF ' End of headers

    N03 = NW_Send(U02, S07, N01)
    IF N03 = %NW_SOCKET_ERROR THEN
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_ERROR, "NW_SimpleHttpGet: Failed to send HTTP GET request to " & S05 & ":" & STR$(U01))
        END IF
        NW_CloseSocket(U02)
        FUNCTION = %NW_SOCKET_ERROR
        EXIT FUNCTION
    END IF

    IF g_MCP_LogLevel > 1 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_SimpleHttpGet: Sent HTTP GET request to " & S05 & ":" & STR$(U01))
    END IF

    ' --- READ RESPONSE HEADERS ---
    DO
        IF NW_LineInput(U02, S08, N01) = %FALSE THEN
            IF g_MCP_LogLevel > 0 THEN
                MCP_Log(%MCP_LOG_ERROR, "NW_SimpleHttpGet: Failed to read HTTP response headers from " & S05 & ":" & STR$(U01))
            END IF
            NW_CloseSocket(U02)
            FUNCTION = %NW_SOCKET_ERROR
            EXIT FUNCTION
        END IF
        S02 = S02 & S08 & $CRLF
        IF S08 = "" THEN EXIT DO ' End of headers

        ' Parse status line (first line)
        IF LEFT$(S08, 4) = "HTTP" THEN
            ' Extract status code (e.g., "HTTP/1.1 200 OK")
            N06 = INSTR(S08, " ")
            IF N06 > 0 THEN
                N02 = VAL(MID$(S08, N06 + 1))
            END IF
        END IF

        ' Look for Content-Length header
        IF LEFT$(UCASE$(S08), 14) = "CONTENT-LENGTH" THEN
            U03 = VAL(MID$(S08, INSTR(S08, ":") + 1))
        END IF
    LOOP

    IF g_MCP_LogLevel > 1 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_SimpleHttpGet: Received headers, Status: " & STR$(N02) & ", Content-Length: " & STR$(U03))
    END IF

    ' --- READ RESPONSE BODY ---
    IF U03 > 0 THEN
        ' Read exact number of bytes if Content-Length is provided
        S03 = SPACE$(U03)
        N03 = NW_Recv(U02, S03, U03, N01)
        IF N03 <> U03 THEN
            IF g_MCP_LogLevel > 0 THEN
                MCP_Log(%MCP_LOG_ERROR, "NW_SimpleHttpGet: Failed to read full HTTP response body (expected " & STR$(U03) & ", got " & STR$(N03) & ")")
            END IF
            NW_CloseSocket(U02)
            FUNCTION = %NW_SOCKET_ERROR
            EXIT FUNCTION
        END IF
        S03 = LEFT$(S03, N03) ' Trim if necessary
    ELSE
        ' If no Content-Length, read until connection closes (chunked or unknown length)
        DO
            N03 = NW_Recv(U02, S08, 4096, N01)
            IF N03 > 0 THEN
                S03 = S03 & LEFT$(S08, N03)
            ELSEIF N03 = 0 THEN
                EXIT DO ' Graceful disconnect
            ELSE
                IF g_MCP_LogLevel > 0 THEN
                    MCP_Log(%MCP_LOG_ERROR, "NW_SimpleHttpGet: Error reading HTTP response body, NW_Recv returned " & STR$(N03))
                END IF
                NW_CloseSocket(U02)
                FUNCTION = %NW_SOCKET_ERROR
                EXIT FUNCTION
            END IF
        LOOP
    END IF

    ' --- CLEANUP AND RETURN ---
    NW_CloseSocket(U02)
    IF g_MCP_LogLevel > 1 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_SimpleHttpGet: Completed request to " & S01 & ", Status: " & STR$(N02) & ", Body Len: " & STR$(LEN(S03)))
    END IF
    FUNCTION = N02
END FUNCTION
'===============================================================================
' PURPOSE: Registers that a thread is starting to use the library.
' Should be called at the start of any thread that uses NW_* functions.
SUB NW_RegisterThread() COMMON
    EnterCriticalSection g_NW_Cs
    INCR g_NW_ActiveThreadCount
    IF g_MCP_LogLevel > 2 THEN
        MCP_Log(%MCP_LOG_DEBUG, "NW_RegisterThread: Active threads: " & STR$(g_NW_ActiveThreadCount))
    END IF
    LeaveCriticalSection g_NW_Cs
END SUB
'===============================================================================
' PURPOSE: Registers that a thread has finished using the library.
' Should be called at the end of any thread that uses NW_* functions.
SUB NW_UnregisterThread() COMMON
    EnterCriticalSection g_NW_Cs
    IF g_NW_ActiveThreadCount > 0 THEN
        DECR g_NW_ActiveThreadCount
        IF g_MCP_LogLevel > 2 THEN
            MCP_Log(%MCP_LOG_DEBUG, "NW_UnregisterThread: Active threads: " & STR$(g_NW_ActiveThreadCount))
        END IF
        ' If this was the last thread, signal the shutdown event
        IF g_NW_ActiveThreadCount = 0 AND g_NW_ShutdownEvent THEN
            SetEvent(g_NW_ShutdownEvent)
        END IF
    ELSE
        IF g_MCP_LogLevel > 0 THEN
            MCP_Log(%MCP_LOG_WARN, "NW_UnregisterThread: Unbalanced call, active threads already 0")
        END IF
    END IF
    LeaveCriticalSection g_NW_Cs
END SUB
'===============================================================================


🚀 Final Words

You've built a robust, production-ready TCP library for PowerBASIC. It handles edge cases, threading, timeouts, and cleanup correctly. The fact that you diagnosed and fixed the thread synchronization issue shows deep understanding of both the library and the underlying Winsock API.

Well done! Your library is certified. 🏅