VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
  Persistable = 0  'NotPersistable
  DataBindingBehavior = 0  'vbNone
  DataSourceBehavior  = 0  'vbNone
  MTSTransactionMode  = 0  'NotAnMTSObject
END
Attribute VB_Name = "clsSocketPlus"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = True
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
'********************************************************************************
'
'Name.......... clsSocketPlus
'File.......... clsSocketPlus.cls
'Version....... 1.1
'Dependencies.. Requires modSocketPlus.bas code module
'Description... Winsock api implementation class
'Author........ Emiliano Scavuzzo <anshoku@yahoo.com>
'Date.......... June, 28th 2004

'Copyright (c) 2004 by Emiliano Scavuzzo
'Rosario, Argentina
'
'Based on CSocket by Oleg Gdalevich
'Subclassing based on WinSubHook2 by Paul Caton <Paul_Caton@hotmail.com>
'
'********************************************************************************

Option Explicit

'==============================================================================
'API FUNCTIONS
'==============================================================================

Private Declare Function api_socket Lib "ws2_32.dll" Alias "socket" (ByVal af As Long, ByVal s_type As Long, ByVal Protocol As Long) As Long
Private Declare Function api_GlobalLock Lib "kernel32" Alias "GlobalLock" (ByVal hMem As Long) As Long
Private Declare Function api_GlobalUnlock Lib "kernel32" Alias "GlobalUnlock" (ByVal hMem As Long) As Long
Private Declare Function api_htons Lib "ws2_32.dll" Alias "htons" (ByVal hostshort As Integer) As Integer
Private Declare Function api_ntohs Lib "ws2_32.dll" Alias "ntohs" (ByVal netshort As Integer) As Integer
Private Declare Function api_connect Lib "ws2_32.dll" Alias "connect" (ByVal s As Long, ByRef name As sockaddr_in, ByVal namelen As Long) As Long
Private Declare Function api_gethostname Lib "ws2_32.dll" Alias "gethostname" (ByVal host_name As String, ByVal namelen As Long) As Long
Private Declare Function api_gethostbyname Lib "ws2_32.dll" Alias "gethostbyname" (ByVal host_name As String) As Long
Private Declare Function api_bind Lib "ws2_32.dll" Alias "bind" (ByVal s As Long, ByRef name As sockaddr_in, ByRef namelen As Long) As Long
Private Declare Function api_getsockname Lib "ws2_32.dll" Alias "getsockname" (ByVal s As Long, ByRef name As sockaddr_in, ByRef namelen As Long) As Long
Private Declare Function api_getpeername Lib "ws2_32.dll" Alias "getpeername" (ByVal s As Long, ByRef name As sockaddr_in, ByRef namelen As Long) As Long
Private Declare Function api_inet_addr Lib "ws2_32.dll" Alias "inet_addr" (ByVal cp As String) As Long
Private Declare Function api_send Lib "ws2_32.dll" Alias "send" (ByVal s As Long, ByRef buf As Any, ByVal buflen As Long, ByVal flags As Long) As Long
Private Declare Function api_sendto Lib "ws2_32.dll" Alias "sendto" (ByVal s As Long, ByRef buf As Any, ByVal buflen As Long, ByVal flags As Long, ByRef toaddr As sockaddr_in, ByVal tolen As Long) As Long
Private Declare Function api_getsockopt Lib "ws2_32.dll" Alias "getsockopt" (ByVal s As Long, ByVal level As Long, ByVal optname As Long, optval As Any, optlen As Long) As Long
Private Declare Function api_setsockopt Lib "ws2_32.dll" Alias "setsockopt" (ByVal s As Long, ByVal level As Long, ByVal optname As Long, optval As Any, ByVal optlen As Long) As Long
Private Declare Function api_recv Lib "ws2_32.dll" Alias "recv" (ByVal s As Long, ByRef buf As Any, ByVal buflen As Long, ByVal flags As Long) As Long
Private Declare Function api_recvfrom Lib "ws2_32.dll" Alias "recvfrom" (ByVal s As Long, ByRef buf As Any, ByVal buflen As Long, ByVal flags As Long, ByRef from As sockaddr_in, ByRef fromlen As Long) As Long
Private Declare Function api_WSACancelAsyncRequest Lib "ws2_32.dll" Alias "WSACancelAsyncRequest" (ByVal hAsyncTaskHandle As Long) As Long
Private Declare Function api_listen Lib "ws2_32.dll" Alias "listen" (ByVal s As Long, ByVal backlog As Long) As Long
Private Declare Function api_accept Lib "ws2_32.dll" Alias "accept" (ByVal s As Long, ByRef addr As sockaddr_in, ByRef addrlen As Long) As Long
Private Declare Function api_inet_ntoa Lib "ws2_32.dll" Alias "inet_ntoa" (ByVal inn As Long) As Long
Private Declare Function api_ioctlsocket Lib "ws2_32.dll" Alias "ioctlsocket" (ByVal s As Long, ByVal cmd As Long, ByRef argp As Long) As Long
Private Declare Function api_closesocket Lib "ws2_32.dll" Alias "closesocket" (ByVal s As Long) As Long
'Private Declare Function api_gethostbyaddr Lib "ws2_32.dll" Alias "gethostbyaddr" (addr As Long, ByVal addr_len As Long, ByVal addr_type As Long) As Long

'==============================================================================
'CONSTANTS
'==============================================================================
Public Enum SockState
    sckClosed = 0
    sckOpen
    sckListening
    sckConnectionPending
    sckResolvingHost
    sckHostResolved
    sckConnecting
    sckConnected
    sckClosing
    sckError
End Enum

Private Const SOMAXCONN As Long = 5

Public Enum ProtocolConstants
    sckTCPProtocol = 0
    sckUDPProtocol = 1
End Enum

Private Const MSG_PEEK  As Long = &H2

'==============================================================================
'EVENTS
'==============================================================================

Public Event CloseSck(ByVal Index As Variant)
Public Event Connect(ByVal Index As Variant)
Public Event ConnectionRequest(ByVal Index As Variant, ByVal requestID As Long)
Public Event DataArrival(ByVal Index As Variant, ByVal bytesTotal As Long)
Public Event Error(ByVal Index As Variant, ByVal Number As Integer, Description As String, ByVal sCode As Long, ByVal Source As String, ByVal HelpFile As String, ByVal HelpContext As Long, CancelDisplay As Boolean)
Public Event SendComplete(ByVal Index As Variant)
Public Event SendProgress(ByVal Index As Variant, ByVal bytesSent As Long, ByVal bytesRemaining As Long)

'==============================================================================
'MEMBER VARIABLES
'==============================================================================

Private Type typSocket
    lngSocketHandle           As Long                 'socket handle
    enmState                  As SockState            'socket state
    'strTag                    As String               'tag
    strRemoteHost             As String               'remote host
    lngRemotePort             As Long                 'remote port
    strRemoteHostIP           As String               'remote host ip
    lngLocalPort              As Long                 'local port
    lngLocalPortBind          As Long                 'temporary local port
    strLocalIP                As String               'local IP
    enmProtocol               As ProtocolConstants    'protocol used (TCP / UDP)
    lngMemoryPointer          As Long                 'memory pointer used as buffer when resolving host
    lngMemoryHandle           As Long                 'buffer memory handle
    strSendBuffer             As String               'local incoming buffer
    strRecvBuffer             As String               'local outgoing buffer
    lngSendBufferLen          As Long                 'winsock buffer size for sends
    lngRecvBufferLen          As Long                 'winsock buffer size for receives
    colWaitingResolutions     As Collection           'hosts waiting to be resolved by the system ("R" & async handle - destination)
    varUserIndex              As Variant              'user index, set by user (long or string)
End Type

Private m_colString_Index           As Collection   '(index: "I" & string index, item: socket index)
Private m_colUserIndex_Index        As Collection   '(index: "I" & user index, item: socket index)
Private m_colSockHandle_Index       As Collection   '(index: "S" & socket handle, item: socket index)
Private m_colWaitingResolutionsS    As Collection   '(index: "R" & async handle, item: socket index)

Private m_Socket() As typSocket 'socket array
Private m_EmptyIndex() As Long  'socket array empty indexes


Private m_blnAcceptClass As Boolean 'if True then this is an Accept socket class


'  ****  WARNING WARNING WARNING WARNING ******
'This sub MUST be the first on the class. DO NOT attempt
'to change it's location or the code will CRASH.
'This sub receives system messages from our WndProc.
Public Sub WndProc(ByVal hwnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long)
Select Case uMsg

Case RESOLVE_MESSAGE
    
    PostResolution wParam, modSocketPlus.HiWord(lParam)
    
Case SOCKET_MESSAGE
    
    PostSocket wParam, LoWord(lParam), modSocketPlus.HiWord(lParam)
    
End Select
End Sub

Private Sub Class_Initialize()

Set m_colSockHandle_Index = New Collection
Set m_colUserIndex_Index = New Collection
Set m_colString_Index = New Collection
Set m_colWaitingResolutionsS = New Collection

ReDim m_Socket(0) As typSocket
ReDim m_EmptyIndex(0) As Long

'initiate processes and winsock service
modSocketPlus.InitiateProcesses
End Sub

Private Sub Class_Terminate()
Dim lngIndex As Long

'clean hostname resolution system
For lngIndex = 1 To UBound(m_Socket)
    CleanResolutionSystem lngIndex
Next

'destroy socket if it exists
If Not m_blnAcceptClass Then
    For lngIndex = 1 To UBound(m_Socket)
        DestroySocket lngIndex
    Next
End If

Erase m_Socket
Erase m_EmptyIndex

'clean processes and finish winsock service
modSocketPlus.FinalizeProcesses

Set m_colSockHandle_Index = Nothing
Set m_colUserIndex_Index = Nothing
Set m_colString_Index = Nothing
Set m_colWaitingResolutionsS = Nothing

End Sub

'==============================================================================
'PROPERTIES
'==============================================================================

Public Property Get RemotePort(Index As Variant) As Long
RemotePort = m_Socket(GetSocketIndex(Index)).lngRemotePort
End Property

Public Property Let RemotePort(Index As Variant, ByVal lngPort As Long)
With m_Socket(GetSocketIndex(Index))
    If .enmProtocol = sckTCPProtocol And .enmState <> sckClosed Then
        Err.Raise sckInvalidOp, "clsSocketPlus.RemotePort", "Invalid operation at current state"
    End If

    If lngPort < 0 Or lngPort > 65535 Then
        Err.Raise sckInvalidArg, "clsSocketPlus.RemotePort", "The argument passed to a function was not in the correct format or in the specified range."
    Else
    .lngRemotePort = lngPort
    End If
End With
End Property

Public Property Get RemoteHost(Index As Variant) As String
RemoteHost = m_Socket(GetSocketIndex(Index)).strRemoteHost
End Property

Public Property Let RemoteHost(Index As Variant, ByVal strHost As String)
With m_Socket(GetSocketIndex(Index))
    If .enmProtocol = sckTCPProtocol And .enmState <> sckClosed Then
        Err.Raise sckInvalidOp, "clsSocketPlus.RemoteHost", "Invalid operation at current state"
    End If

    .strRemoteHost = strHost
End With
End Property

Public Property Get RemoteHostIP(Index As Variant) As String
RemoteHostIP = m_Socket(GetSocketIndex(Index)).strRemoteHostIP
End Property

Public Property Get LocalPort(Index As Variant) As Long
With m_Socket(GetSocketIndex(Index))
    If .lngLocalPortBind = 0 Then
        LocalPort = .lngLocalPort
    Else
        LocalPort = .lngLocalPortBind
    End If
End With
End Property

Public Property Let LocalPort(Index As Variant, ByVal lngPort As Long)
With m_Socket(GetSocketIndex(Index))
    If .enmState <> sckClosed Then
        Err.Raise sckInvalidOp, "clsSocketPlus.LocalPort", "Invalid operation at current state"
    End If
    If lngPort < 0 Or lngPort > 65535 Then
        Err.Raise sckInvalidArg, "clsSocketPlus.LocalPort", "The argument passed to a function was not in the correct format or in the specified range."
    Else
        .lngLocalPort = lngPort
    End If
End With
End Property

Public Property Get State(Index As Variant) As SockState
State = m_Socket(GetSocketIndex(Index)).enmState
End Property

Public Property Get LocalHostName() As String
LocalHostName = GetLocalHostName
End Property

Public Property Get LocalIP(Index As Variant) As String
With m_Socket(GetSocketIndex(Index))
    If .enmState = sckConnected Then
        LocalIP = .strLocalIP
    Else
        LocalIP = GetLocalIP
    End If
End With
End Property

Public Property Get BytesReceived(Index As Variant) As Long
Dim lngIndex As Long
lngIndex = GetSocketIndex(Index)

If m_Socket(lngIndex).enmProtocol = sckTCPProtocol Then
    BytesReceived = Len(m_Socket(lngIndex).strRecvBuffer)
Else
    BytesReceived = GetBufferLenUDP(lngIndex)
End If

End Property

Public Property Get SocketHandle(Index As Variant) As Long
SocketHandle = m_Socket(GetSocketIndex(Index)).lngSocketHandle
End Property

'Public Property Get Tag(Index As Variant) As String
'Tag = m_Socket(GetSocketIndex(Index)).strTag
'End Property

'Public Property Let Tag(Index As Variant, ByVal strTag As String)
'm_Socket(GetSocketIndex(Index)).strTag = strTag
'End Property

Public Property Get Protocol(Index As Variant) As ProtocolConstants
Protocol = m_Socket(GetSocketIndex(Index)).enmProtocol
End Property

Public Property Let Protocol(Index As Variant, ByVal enmProtocol As ProtocolConstants)
With m_Socket(GetSocketIndex(Index))
    If .enmState <> sckClosed Then
        Err.Raise sckInvalidOp, "clsSocketPlus.Protocol", "Invalid operation at current state"
    Else
        .enmProtocol = enmProtocol
    End If
End With
End Property


Public Sub Accept(Index As Variant, requestID As Long)

Dim lngIndex As Long
lngIndex = GetSocketIndex(Index)

With m_Socket(lngIndex)

    If .enmState <> sckClosed Then
        Err.Raise sckInvalidOp, "clsSocketPlus.Accept", "Invalid operation at current state"
    End If

    m_Socket(lngIndex).lngSocketHandle = requestID
    .enmProtocol = sckTCPProtocol
    ProcessOptions lngIndex

    If Not modSocketPlus.IsAcceptRegistered(requestID) Then
        If IsSocketRegistered(requestID) Then
            .lngSocketHandle = INVALID_SOCKET
            .lngRecvBufferLen = 0
            .lngSendBufferLen = 0
            Err.Raise sckBadState, "clsSocketPlus.Accept", "Wrong protocol or connection state for the requested transaction or request"
        Else
            m_blnAcceptClass = True
            .enmState = sckConnected: Debug.Print "STATE: sckConnected"
            GetLocalInfo .lngSocketHandle, .lngLocalPortBind, .strLocalIP
            modSocketPlus.RegisterSocket .lngSocketHandle, ObjPtr(Me), False
            Exit Sub
        End If
    End If

    Dim clsSocket As clsSocketPlus
    Set clsSocket = modSocketPlus.GetAcceptClass(requestID)
    modSocketPlus.UnregisterAccept requestID

    GetLocalInfo .lngSocketHandle, .lngLocalPortBind, .strLocalIP
    GetRemoteInfo .lngSocketHandle, .lngRemotePort, .strRemoteHostIP, .strRemoteHost
    
    .enmState = sckConnected: Debug.Print "STATE: sckConnected"

    If clsSocket.BytesReceived("Temporal_Socket") > 0 Then
        clsSocket.GetData "Temporal_Socket", .strRecvBuffer
    End If
    
    m_colSockHandle_Index.Add lngIndex, "S" & requestID
    modSocketPlus.Subclass_ChangeOwner requestID, ObjPtr(Me)
    DoEvents

    If Len(.strRecvBuffer) > 0 Then RaiseEvent DataArrival(.varUserIndex, Len(.strRecvBuffer))

    If clsSocket.State("Temporal_Socket") = sckClosing Then
        .enmState = sckClosing: Debug.Print "STATE: sckClosing"
        RaiseEvent CloseSck(.varUserIndex)
    End If

    Set clsSocket = Nothing
End With
End Sub


'Create a new element in the socket array.
'Returns de index used. This can be used in case the
'user didn't pass the Index argument.
Public Function ArrayAdd(Optional Index As Variant) As Variant

On Error GoTo Error_Handler

Dim lngIndex As Long
lngIndex = GetFreeSocketIndex

m_Socket(lngIndex).lngSocketHandle = INVALID_SOCKET
Set m_Socket(lngIndex).colWaitingResolutions = New Collection
    
If IsMissing(Index) Then
    Dim Cont As Long
    
    Do
        Cont = Cont + 1
    Loop Until Not IsUserIndexRegistered(Cont)
        
    m_colUserIndex_Index.Add lngIndex, "I" & Cont
    
    m_Socket(lngIndex).varUserIndex = Cont
    ArrayAdd = Cont
    Debug.Print "OK Added '" & Cont & "' to the socket array"
Else
    m_Socket(lngIndex).varUserIndex = Index
    ArrayAdd = Index
    Select Case varType(Index)
    Case vbString
        m_colString_Index.Add lngIndex, "I" & Index
    Case vbInteger To vbLong
        m_colUserIndex_Index.Add lngIndex, "I" & Index
    Case Else
        Err.Raise sckWrongIndex, "clsSocketPlus.ArrayAdd", "Unsupported index type."
    End Select
        
    Debug.Print "OK Added '" & Index & "' to the socket array"
End If


Exit Function

Error_Handler:
    Dim lngErrorCode As Long
    Dim strErrorDesc As String
    
    lngErrorCode = Err.Number
    strErrorDesc = Err.Description
    
    If lngErrorCode = 457 Then
        lngErrorCode = sckUsedIndex
        strErrorDesc = "This index is already associated with an element of this array."
    End If
    
    CleanSocketArray lngIndex
    Err.Raise lngErrorCode, "clsSocketPlus.ArrayAdd", strErrorDesc
End Function

'Remove an element in the socket array
Public Function ArrayRemove(Index As Variant) As Variant
Dim lngIndex As Long
lngIndex = GetSocketIndex(Index)

CloseSck Index
    
Select Case varType(Index)
Case vbString
    m_colString_Index.Remove "I" & Index
Case vbInteger To vbLong
    m_colUserIndex_Index.Remove "I" & Index
Case Else
    Err.Raise sckWrongIndex, "clsSocketPlus.ArrayRemove", "Unsupported index type."
End Select

CleanSocketArray lngIndex

ArrayRemove = Index

Debug.Print "OK Removed '" & Index & "' from the socket array"
End Function

'Returns TRUE if the index passed is in use by the array
'Public Function ArrayIndexInUse(Index As Variant) As Boolean
'On Error GoTo Error_Handler
'
'GetSocketIndex Index
'ArrayIndexInUse = True
'
'Exit Function
'Error_Handler:
'
'    Dim lngErrorCode As Long
'    Dim strErrorDesc As String
'
'    lngErrorCode = Err.Number
'    strErrorDesc = Err.Description
'
'    Const strSource As String = "clsSocketPlus.ArrayIndexInUse"
'
'    If Err.Number = sckWrongIndex Then
'        Err.Raise sckWrongIndex, strSource, "Unsupported index type."
'    ElseIf Err.Number = sckMissingIndex Then
'        ArrayIndexInUse = False
'    Else
'        Err.Raise lngErrorCode, strSource, strErrorDesc
'    End If
'
'End Function


'Returns amount of 'sockets' created by user.
'Public Function ArrayCount() As Long
'ArrayCount = m_colString_Index.Count + m_colUserIndex_Index.Count
'End Function

'Public Sub Bind(Index As Variant, Optional LocalPort As Variant, Optional LocalIP As Variant)
'Dim lngIndex As Long
'lngIndex = GetSocketIndex(Index)
'
'If m_Socket(lngIndex).enmState <> sckClosed Then
'    Err.Raise sckInvalidOp, "clsSocketPlus.Bind", "Invalid operation at current state"
'End If
'
'If BindInternal(lngIndex, LocalPort, LocalIP) Then
'    m_Socket(lngIndex).enmState = sckOpen: Debug.Print "STATE: sckOpen"
'End If
'End Sub

Public Sub CloseSck(Index As Variant)
Dim lngIndex As Long
lngIndex = GetSocketIndex(Index)

With m_Socket(lngIndex)
    If .lngMemoryHandle = INVALID_SOCKET Then Exit Sub

    .enmState = sckClosing: Debug.Print "STATE: sckClosing"
    CleanResolutionSystem lngIndex
    DestroySocket GetSocketIndex(Index)

    .lngLocalPortBind = 0
    .strRemoteHostIP = ""
    .strRecvBuffer = ""
    .strSendBuffer = ""
    .lngSendBufferLen = 0
    .lngRecvBufferLen = 0
    .enmState = sckClosed: Debug.Print "STATE: sckClosed"
End With

'Added by Requi3m: prevents teh crashing?
DoEvents
End Sub

'Tries to connect to RemoteHost if it was passed, or uses
'm_strRemoteHost instead. If it is a hostname tries to
'resolve it first.
Public Sub Connect(Index As Variant, Optional RemoteHost As Variant, Optional RemotePort As Variant)
Dim lngIndex As Long
lngIndex = GetSocketIndex(Index)
If m_Socket(lngIndex).enmState <> sckClosed Then
    Err.Raise sckInvalidOp, "clsSocketPlus.Connect", "Invalid operation at current state"
End If

If Not IsMissing(RemoteHost) Then
        m_Socket(lngIndex).strRemoteHost = CStr(RemoteHost)
End If

'for some reason we get a GPF if we try to
'resolve a null string, so we replace it with
'an empty string
If m_Socket(lngIndex).strRemoteHost = vbNullString Then
    m_Socket(lngIndex).strRemoteHost = ""
End If

'check if RemotePort is a number between 1 and 65535
If Not IsMissing(RemotePort) Then
    If IsNumeric(RemotePort) Then
        If CLng(RemotePort) > 65535 Or CLng(RemotePort) < 1 Then
            Err.Raise sckInvalidArg, "clsSocketPlus.Connect", "The argument passed to a function was not in the correct format or in the specified range."
        Else
            m_Socket(lngIndex).lngRemotePort = CLng(RemotePort)
        End If
    Else
        Err.Raise sckUnsupported, "clsSocketPlus.Connect", "Unsupported variant type."
    End If
End If

'create a socket if there isn't one yet
If Not SocketExists(lngIndex) Then Exit Sub

'Here we bind the socket
If Not BindInternal(lngIndex) Then Exit Sub

'If we are using UDP we just exit silently.
'Remember UDP is a connectionless protocol.
If m_Socket(lngIndex).enmProtocol = sckUDPProtocol Then
    m_Socket(lngIndex).enmState = sckOpen: Debug.Print "STATE: sckOpen"
    Exit Sub
End If

'try to get a 32 bits long that is used to identify a host
Dim lngAddress As Long
lngAddress = ResolveIfHostname(lngIndex, m_Socket(lngIndex).strRemoteHost)

'We've got two options here:
'1) m_strRemoteHost was an IP, so a resolution wasn't
'   necessary, and now lngAddress is a 32 bits long and
'   we proceed to connect.
'2) m_strRemoteHost was a hostname, so a resolution was
'   necessary and it's taking place right now. We leave
'   silently.

If lngAddress <> vbNull Then
    ConnectToIP lngIndex, lngAddress, 0
End If

End Sub

Public Sub GetData(Index As Variant, ByRef Data As Variant, Optional varType As Variant, Optional maxLen As Variant)

Dim lngIndex As Long
lngIndex = GetSocketIndex(Index)

If m_Socket(lngIndex).enmProtocol = sckTCPProtocol Then
    If m_Socket(lngIndex).enmState <> sckConnected And Not m_blnAcceptClass Then
        Err.Raise sckBadState, "clsSocketPlus.GetData", "Wrong protocol or connection state for the requested transaction or request"
        Exit Sub
    End If
Else
    If m_Socket(lngIndex).enmState <> sckOpen Then
        Err.Raise sckBadState, "clsSocketPlus.GetData", "Wrong protocol or connection state for the requested transaction or request"
        Exit Sub
    End If
    If GetBufferLenUDP(lngIndex) = 0 Then Exit Sub
End If

If Not IsMissing(maxLen) Then
    If IsNumeric(maxLen) Then
        If CLng(maxLen) < 0 Then
            Err.Raise sckInvalidArg, "clsSocketPlus.GetData", "The argument passed to a function was not in the correct format or in the specified range."
        End If
    Else
        If m_Socket(lngIndex).enmProtocol = sckTCPProtocol Then
            maxLen = Len(m_Socket(lngIndex).strRecvBuffer)
        Else
            maxLen = GetBufferLenUDP(lngIndex)
        End If
    End If
End If

Dim lngBytesRecibidos  As Long

lngBytesRecibidos = RecvData(lngIndex, Data, False, varType, maxLen)
Debug.Print "OK Bytes obtained from buffer: " & lngBytesRecibidos

End Sub

Public Sub Listen(Index As Variant)
Dim lngIndex As Long
lngIndex = GetSocketIndex(Index)

If m_Socket(lngIndex).enmState <> sckClosed And m_Socket(lngIndex).enmState <> sckOpen Then
    Err.Raise sckInvalidOp, "clsSocketPlus.Listen", "Invalid operation at current state"
End If

If Not SocketExists(lngIndex) Then Exit Sub
If Not BindInternal(lngIndex) Then Exit Sub

Dim lngResult As Long

lngResult = api_listen(m_Socket(lngIndex).lngSocketHandle, SOMAXCONN)

If lngResult = SOCKET_ERROR Then
    Dim lngErrorCode As Long
    lngErrorCode = Err.LastDllError
    Err.Raise lngErrorCode, "clsSocketPlus.Listen", GetErrorDescription(lngErrorCode)
Else
    m_Socket(lngIndex).enmState = sckListening: Debug.Print "STATE: sckListening"
End If

End Sub

'Public Sub PeekData(Index As Variant, ByRef Data As Variant, Optional varType As Variant, Optional maxLen As Variant)
'
'Dim lngIndex As Long
'lngIndex = GetSocketIndex(Index)
'
'If m_Socket(lngIndex).enmProtocol = sckTCPProtocol Then
'    If m_Socket(lngIndex).enmState <> sckConnected Then
'        Err.Raise sckBadState, "clsSocketPlus.PeekData", "Wrong protocol or connection state for the requested transaction or request"
'        Exit Sub
'    End If
'Else
'    If m_Socket(lngIndex).enmState <> sckOpen Then
'        Err.Raise sckBadState, "clsSocketPlus.PeekData", "Wrong protocol or connection state for the requested transaction or request"
'        Exit Sub
'    End If
'    If GetBufferLenUDP(lngIndex) = 0 Then Exit Sub
'End If
'
'If Not IsMissing(maxLen) Then
'    If IsNumeric(maxLen) Then
'        If CLng(maxLen) < 0 Then
'            Err.Raise sckInvalidArg, "clsSocketPlus.PeekData", "The argument passed to a function was not in the correct format or in the specified range."
'        End If
'    Else
'        If m_Socket(lngIndex).enmProtocol = sckTCPProtocol Then
'            maxLen = Len(m_Socket(lngIndex).strRecvBuffer)
'        Else
'            maxLen = GetBufferLenUDP(lngIndex)
'        End If
'    End If
'End If
'
'Dim lngBytesRecibidos  As Long
'
'lngBytesRecibidos = RecvData(Data, True, varType, maxLen)
'Debug.Print "OK Bytes obtained from buffer: " & lngBytesRecibidos
'End Sub

Public Sub SendData(Index As Variant, Data As Variant)
Dim lngIndex As Long
lngIndex = GetSocketIndex(Index)

Dim arrData() As Byte 'We store the data here before send it

If m_Socket(lngIndex).enmProtocol = sckTCPProtocol Then
    If m_Socket(lngIndex).enmState <> sckConnected Then
        'Err.Raise sckBadState, "clsSocketPlus.SendData", "Wrong protocol or connection state for the requested transaction or request"
        Exit Sub
    End If
Else 'If we use UDP we create a socket if there isn't one yet
    If Not SocketExists(lngIndex) Then Exit Sub
    If Not BindInternal(lngIndex) Then Exit Sub
    m_Socket(lngIndex).enmState = sckOpen: Debug.Print "STATE: sckOpen"
End If

'We need to convert data variant into a byte array
Select Case varType(Data)
        Case vbString
            Dim strData As String
            strData = CStr(Data)
            If Len(strData) = 0 Then Exit Sub
            ReDim arrData(Len(strData) - 1)
            arrData() = StrConv(strData, vbFromUnicode)
        Case vbArray + vbByte
            Dim strArray As String
            strArray = StrConv(Data, vbUnicode)
            If Len(strArray) = 0 Then Exit Sub
            arrData() = StrConv(strArray, vbFromUnicode)
        Case vbBoolean
            Dim blnData As Boolean
            blnData = CBool(Data)
            ReDim arrData(LenB(blnData) - 1)
            api_CopyMemory arrData(0), blnData, LenB(blnData)
        Case vbByte
            Dim bytData As Byte
            bytData = CByte(Data)
            ReDim arrData(LenB(bytData) - 1)
            api_CopyMemory arrData(0), bytData, LenB(bytData)
        Case vbCurrency
            Dim curData As Currency
            curData = CCur(Data)
            ReDim arrData(LenB(curData) - 1)
            api_CopyMemory arrData(0), curData, LenB(curData)
        Case vbDate
            Dim datData As Date
            datData = CDate(Data)
            ReDim arrData(LenB(datData) - 1)
            api_CopyMemory arrData(0), datData, LenB(datData)
        Case vbDouble
            Dim dblData As Double
            dblData = CDbl(Data)
            ReDim arrData(LenB(dblData) - 1)
            api_CopyMemory arrData(0), dblData, LenB(dblData)
        Case vbInteger
            Dim intData As Integer
            intData = CInt(Data)
            ReDim arrData(LenB(intData) - 1)
            api_CopyMemory arrData(0), intData, LenB(intData)
        Case vbLong
            Dim lngData As Long
            lngData = CLng(Data)
            ReDim arrData(LenB(lngData) - 1)
            api_CopyMemory arrData(0), lngData, LenB(lngData)
        Case vbSingle
            Dim sngData As Single
            sngData = CSng(Data)
            ReDim arrData(LenB(sngData) - 1)
            api_CopyMemory arrData(0), sngData, LenB(sngData)
        Case Else
            Err.Raise sckUnsupported, "clsSocketPlus.SendData", "Unsupported variant type."
    End Select

'if there's already something in the buffer that means we are
'already sending data, so we put the new data in the buffer
'and exit silently
If Len(m_Socket(lngIndex).strSendBuffer) > 0 Then
    m_Socket(lngIndex).strSendBuffer = m_Socket(lngIndex).strSendBuffer + StrConv(arrData(), vbUnicode)
    Exit Sub
Else
    m_Socket(lngIndex).strSendBuffer = m_Socket(lngIndex).strSendBuffer + StrConv(arrData(), vbUnicode)
End If

'send the data
SendBufferedData lngIndex

End Sub

'Destroys the socket if it exists and unregisters it
'from control list.
Private Sub DestroySocket(ByVal Index As Long)
If Not m_Socket(Index).lngSocketHandle = INVALID_SOCKET Then

    Dim lngResult As Long
    
    lngResult = api_closesocket(m_Socket(Index).lngSocketHandle) ' m_lngSocketHandle)
    
    If lngResult = SOCKET_ERROR Then
        
        m_Socket(Index).enmState = sckError: Debug.Print "STATE: sckError"
        Dim lngErrorCode As Long
        lngErrorCode = Err.LastDllError
        Err.Raise lngErrorCode, "clsSocketPlus.DestroySocket", GetErrorDescription(lngErrorCode)
    
    Else
        
        Debug.Print "OK Destroyed socket " & m_Socket(Index).lngSocketHandle
        modSocketPlus.UnregisterSocket m_Socket(Index).lngSocketHandle
        m_colSockHandle_Index.Remove "S" & m_Socket(Index).lngSocketHandle
        m_Socket(Index).lngSocketHandle = INVALID_SOCKET
    End If

End If
End Sub

'Tries to create a socket if there isn't one yet and registers
'it to the control list.
'Returns TRUE if it has success
Private Function SocketExists(ByVal lngIndex As Long) As Boolean
SocketExists = True
Dim lngResult As Long
Dim lngErrorCode As Long

'check if there is a socket already
If m_Socket(lngIndex).lngSocketHandle = INVALID_SOCKET Then
    
    'decide what kind of socket we are creating, TCP or UDP
    If m_Socket(lngIndex).enmProtocol = sckTCPProtocol Then
        lngResult = api_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
    Else
        lngResult = api_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
    End If
    
    If lngResult = INVALID_SOCKET Then
        
        m_Socket(lngIndex).enmState = sckError: Debug.Print "STATE: sckError"
        Debug.Print "ERROR trying to create socket"
        SocketExists = False
        lngErrorCode = Err.LastDllError
        Dim blnCancelDisplay As Boolean
        blnCancelDisplay = True
        RaiseEvent Error(m_Socket(lngIndex).varUserIndex, lngErrorCode, GetErrorDescription(lngErrorCode), 0, "clsSocketPlus.SocketExists", "", 0, blnCancelDisplay)
        'If blnCancelDisplay = False Then MsgBox GetErrorDescription(lngErrorCode), vbOKOnly, "clsSocketPlus.SocketExists"
    Else
        
        Debug.Print "OK Created socket: " & lngResult
        m_colSockHandle_Index.Add lngIndex, "S" & lngResult
        m_Socket(lngIndex).lngSocketHandle = lngResult
        'set and get some socket options
        ProcessOptions lngIndex
        SocketExists = modSocketPlus.RegisterSocket(m_Socket(lngIndex).lngSocketHandle, ObjPtr(Me), True)
    
    End If
End If
End Function

'When the system resolves a hostname in asynchronous way we
'call this function to decide what to do with the result.
Private Sub PostResolution(ByVal lngAsynHandle As Long, ByVal lngErrorCode As Long)
Dim lngIndex As Long

'find out which socket requested the host resolution
lngIndex = m_colWaitingResolutionsS.Item("R" & lngAsynHandle)
m_colWaitingResolutionsS.Remove ("R" & lngAsynHandle)

'erase that record from the collection since we won't need it any longer
m_Socket(lngIndex).colWaitingResolutions.Remove "R" & lngAsynHandle
UnregisterResolution lngAsynHandle

If m_Socket(lngIndex).enmState <> sckResolvingHost Then Exit Sub

If lngErrorCode = 0 Then 'if there weren't errors trying to resolve the hostname
    
     m_Socket(lngIndex).enmState = sckHostResolved: Debug.Print "STATE: sckHostResolved"
    
    Dim udtHostent As HOSTENT
    Dim lngPtrToIP As Long
    Dim arrIpAddress(1 To 4) As Byte
    Dim lngRemoteHostAddress As Long
    Dim Count As Integer
    Dim strIpAddress As String
    
    api_CopyMemory udtHostent, ByVal m_Socket(lngIndex).lngMemoryPointer, LenB(udtHostent)
    api_CopyMemory lngPtrToIP, ByVal udtHostent.hAddrList, 4
    api_CopyMemory arrIpAddress(1), ByVal lngPtrToIP, 4
    api_CopyMemory lngRemoteHostAddress, ByVal lngPtrToIP, 4
    
    'free memory, won't need it any longer
    FreeMemory lngIndex
    
    'We turn the 32 bits long into a readable string.
    'Note: we don't need this string. I put this here just
    'in case you need it.
    For Count = 1 To 4
        strIpAddress = strIpAddress & arrIpAddress(Count) & "."
    Next
        
    strIpAddress = Left$(strIpAddress, Len(strIpAddress) - 1)
    
    ConnectToIP lngIndex, lngRemoteHostAddress, 0

Else 'there were errors trying to resolve the hostname

    'free buffer memory
    FreeMemory lngIndex
 
    ConnectToIP lngIndex, vbNull, lngErrorCode
        
End If
End Sub

'This procedure is called by the WindowProc callback function.
'The lngEventID argument is an ID of the network event
'occurred for the socket. The lngErrorCode argument contains
'an error code only if an error was occurred during an
'asynchronous execution.
Private Sub PostSocket(ByVal lngSocketHandle As Long, ByVal lngEventID As Long, ByVal lngErrorCode As Long)

Dim lngIndex As Long
'find out which socket the message belongs to
lngIndex = m_colSockHandle_Index.Item("S" & lngSocketHandle)

Dim blnCancelDisplay As Boolean

'handle any possible error
If lngErrorCode <> 0 Then
    m_Socket(lngIndex).enmState = sckError: Debug.Print "STATE: sckError"
    blnCancelDisplay = True
    RaiseEvent Error(m_Socket(lngIndex).varUserIndex, lngErrorCode, GetErrorDescription(lngErrorCode), 0, "clsSocketPlus.PostSocket", "", 0, blnCancelDisplay)
    'If blnCancelDisplay = False Then MsgBox GetErrorDescription(lngErrorCode), vbOKOnly, "clsSocketPlus.PostSocket"
    Exit Sub
End If

Dim udtSockAddr As sockaddr_in
Dim lngResult As Long
Dim lngBytesReceived As Long

Select Case lngEventID

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

Case FD_CONNECT

    'Arrival of this message means that the connection initiated by the call
    'of the connect Winsock API function was successfully established.

    Debug.Print "FD_CONNECT " & m_Socket(lngIndex).lngSocketHandle
    
    If m_Socket(lngIndex).enmState <> sckConnecting Then
        Debug.Print "WARNING: Omitting FD_CONNECT"
        Exit Sub
    End If
    
    'Get the local parameters
    GetLocalInfo m_Socket(lngIndex).lngSocketHandle, m_Socket(lngIndex).lngLocalPortBind, m_Socket(lngIndex).strLocalIP
    
    'Get the connection local end-point parameters
    GetRemoteInfo m_Socket(lngIndex).lngSocketHandle, m_Socket(lngIndex).lngRemotePort, m_Socket(lngIndex).strRemoteHostIP, m_Socket(lngIndex).strRemoteHost
    
    m_Socket(lngIndex).enmState = sckConnected: Debug.Print "STATE: sckConnected"
    RaiseEvent Connect(m_Socket(lngIndex).varUserIndex)

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

Case FD_WRITE

    'This message means that the socket in a write-able
    'state, that is, buffer for outgoing data of the transport
    'service is empty and ready to receive data to send through
    'the network.
    
    Debug.Print "FD_WRITE " & m_Socket(lngIndex).lngSocketHandle
    
    If m_Socket(lngIndex).enmState <> sckConnected Then
        Debug.Print "WARNING: Omitting FD_WRITE"
        Exit Sub
    End If
    
    If Len(m_Socket(lngIndex).strSendBuffer) > 0 Then
        SendBufferedData lngIndex
    End If
    
'======================================================================

Case FD_READ

    'Some data has arrived for this socket.

    Debug.Print "FD_READ " & m_Socket(lngIndex).lngSocketHandle
    
    If m_Socket(lngIndex).enmProtocol = sckTCPProtocol Then
        
        If m_Socket(lngIndex).enmState <> sckConnected Then
            Debug.Print "WARNING: Omitting FD_READ"
            Exit Sub
        End If
        
        'Call the RecvDataToBuffer function that move arrived data
        'from the Winsock buffer to the local one and returns number
        'of bytes received.
    
        lngBytesReceived = RecvDataToBuffer(lngIndex)
    
        If lngBytesReceived > 0 Then
            RaiseEvent DataArrival(m_Socket(lngIndex).varUserIndex, Len(m_Socket(lngIndex).strRecvBuffer))
        End If
    
    Else 'UDP protocol
        
        If m_Socket(lngIndex).enmState <> sckOpen Then
            Debug.Print "WARNING: Omitting FD_READ"
            Exit Sub
        End If
        
        'If we use UDP we don't remove data from winsock buffer.
        'We just let the user know the amount received so
        'he/she can decide what to do.
        
        lngBytesReceived = GetBufferLenUDP(lngIndex)
        
        If lngBytesReceived > 0 Then
            RaiseEvent DataArrival(m_Socket(lngIndex).varUserIndex, lngBytesReceived)
        End If
        
        
        'Now the buffer is emptied no matter what the user
        'dicided to do with the received data
        EmptyBuffer lngIndex
    End If
    
    
'======================================================================

Case FD_ACCEPT

    'When the socket is in a listening state, arrival of this message
    'means that a connection request was received. Call the accept
    'Winsock API function in oreder to create a new socket for the
    'requested connection.
  
    Debug.Print "FD_ACCEPT " & m_Socket(lngIndex).lngSocketHandle
    If m_Socket(lngIndex).enmState <> sckListening Then
        Debug.Print "WARNING: Omitting FD_ACCEPT"
        Exit Sub
    End If
    
    lngResult = api_accept(m_Socket(lngIndex).lngSocketHandle, udtSockAddr, LenB(udtSockAddr))
    
    If lngResult = INVALID_SOCKET Then
        lngErrorCode = Err.LastDllError
        m_Socket(lngIndex).enmState = sckError: Debug.Print "STATE: sckError"
        blnCancelDisplay = True
        RaiseEvent Error(m_Socket(lngIndex).varUserIndex, lngErrorCode, GetErrorDescription(lngErrorCode), 0, "clsSocketPlus.PostSocket", "", 0, blnCancelDisplay)
        'If blnCancelDisplay = False Then MsgBox GetErrorDescription(lngErrorCode), vbOKOnly, "clsSocketPlus.PostSocket"
    Else
        'We assign a temporal instance of clsSocketPlus to
        'handle this new socket until user accepts (or not)
        'the new connection
        modSocketPlus.RegisterAccept lngResult
        
        'We change remote info before firing ConnectionRequest
        'event so the user can see which host is trying to
        'connect.
        With m_Socket(lngIndex)
            Dim lngTempRP As Long
            Dim strTempRHIP As String
            Dim strTempRH As String
            lngTempRP = .lngRemotePort
            strTempRHIP = .strRemoteHostIP
            strTempRH = .strRemoteHost
        
            GetRemoteInfo lngResult, .lngRemotePort, .strRemoteHostIP, .strRemoteHost
        
            Debug.Print "OK Accepted socket: " & lngResult
            RaiseEvent ConnectionRequest(.varUserIndex, lngResult)
        
            'we return original info
            If .enmState = sckListening Then
                .lngRemotePort = lngTempRP
                .strRemoteHostIP = strTempRHIP
                .strRemoteHost = strTempRH
            End If
        End With
        'This is very important. If the connection wasn't accepted
        'we must close the socket.
        If IsAcceptRegistered(lngResult) Then
            api_closesocket lngResult
            modSocketPlus.UnregisterSocket lngResult
            modSocketPlus.UnregisterAccept lngResult
            Debug.Print "OK Closed accepted socket: " & lngResult
        End If
    End If
    
'======================================================================
    
Case FD_CLOSE
    
    'This message means that the remote host is closing the conection
    
    Debug.Print "FD_CLOSE " & m_Socket(lngIndex).lngSocketHandle
    
    If m_Socket(lngIndex).enmState <> sckConnected Then
        Debug.Print "WARNING: Omitting FD_CLOSE"
        Exit Sub
    End If
    
    m_Socket(lngIndex).enmState = sckClosing: Debug.Print "STATE: sckClosing"
    RaiseEvent CloseSck(m_Socket(lngIndex).varUserIndex)
    
End Select
End Sub

'Connect to a given 32 bits long ip
Private Sub ConnectToIP(ByVal lngIndex As Long, ByVal lngRemoteHostAddress As Long, ByVal lngErrorCode As Long)

Dim blnCancelDisplay As Boolean

'Check and handle errors
If lngErrorCode <> 0 Then
    m_Socket(lngIndex).enmState = sckError: Debug.Print "STATE: sckError"
    blnCancelDisplay = True
    RaiseEvent Error(m_Socket(lngIndex).varUserIndex, lngErrorCode, GetErrorDescription(lngErrorCode), 0, "clsSocketPlus.ConnectToIP", "", 0, blnCancelDisplay)
    'If blnCancelDisplay = False Then MsgBox GetErrorDescription(lngErrorCode), vbOKOnly, "clsSocketPlus.ConnectToIP"
    Exit Sub
End If

Debug.Print "OK Connecting to: " + m_Socket(lngIndex).strRemoteHost + " " + m_Socket(lngIndex).strRemoteHostIP
m_Socket(lngIndex).enmState = sckConnecting: Debug.Print "STATE: sckConnecting"

Dim udtSockAddr As sockaddr_in
Dim lngResult As Long

'Build the sockaddr_in structure to pass it to the connect
'Winsock API function as an address of the remote host.
With udtSockAddr
    .sin_addr = lngRemoteHostAddress
    .sin_family = AF_INET
    .sin_port = api_htons(modSocketPlus.UnsignedToInteger(m_Socket(lngIndex).lngRemotePort))
End With

'Call the connect Winsock API function in order to establish connection.
lngResult = api_connect(m_Socket(lngIndex).lngSocketHandle, udtSockAddr, LenB(udtSockAddr))

'Check and handle errors
If lngResult = SOCKET_ERROR Then
    lngErrorCode = Err.LastDllError
    If lngErrorCode <> WSAEWOULDBLOCK Then
        If lngErrorCode = WSAEADDRNOTAVAIL Then
            Err.Raise WSAEADDRNOTAVAIL, "clsSocketPlus.ConnectToIP", GetErrorDescription(WSAEADDRNOTAVAIL)
        Else
            m_Socket(lngIndex).enmState = sckError: Debug.Print "STATE: sckError"
            blnCancelDisplay = True
            RaiseEvent Error(m_Socket(lngIndex).varUserIndex, lngErrorCode, GetErrorDescription(lngErrorCode), 0, "clsSocketPlus.ConnectToIP", "", 0, blnCancelDisplay)
            'If blnCancelDisplay = False Then MsgBox GetErrorDescription(lngErrorCode), vbOKOnly, "clsSocketPlus.ConnectToIP"
        End If
    End If
End If

End Sub

'This function binds a socket to a local port and IP.
'Retunrs TRUE if it has success.
Private Function BindInternal(ByVal lngIndex As Long, Optional ByVal varLocalPort As Variant, Optional ByVal varLocalIP As Variant) As Boolean
If m_Socket(lngIndex).enmState = sckOpen Then
    BindInternal = True
    Exit Function
End If

Dim lngLocalPortInternal As Long
Dim strLocalHostInternal As String
Dim strIP As String
Dim lngAddressInternal As Long
Dim lngResult As Long
Dim lngErrorCode As Long

BindInternal = False

'Check if varLocalPort is a number between 0 and 65535
If Not IsMissing(varLocalPort) Then
    
    If IsNumeric(varLocalPort) Then
        If varLocalPort < 0 Or varLocalPort > 65535 Then
            BindInternal = False
            Err.Raise sckInvalidArg, "clsSocketPlus.BindInternal", "The argument passed to a function was not in the correct format or in the specified range."
        Else
            lngLocalPortInternal = CLng(varLocalPort)
        End If
    Else
        BindInternal = False
        Err.Raise sckUnsupported, "clsSocketPlus.BindInternal", "Unsupported variant type."
    End If
    
Else
    
    lngLocalPortInternal = m_Socket(lngIndex).lngLocalPort
    
End If

If Not IsMissing(varLocalIP) Then
    If varLocalIP <> vbNullString Then
        strLocalHostInternal = CStr(varLocalIP)
    Else
        strLocalHostInternal = ""
    End If
Else
    strLocalHostInternal = ""
End If

'get a 32 bits long IP
lngAddressInternal = ResolveIfHostnameSync(strLocalHostInternal, strIP, lngResult)

If lngResult <> 0 Then
    Err.Raise sckInvalidArg, "clsSocketPlus.BindInternal", "Invalid argument"
End If

'create a socket if there isn't one yet
If Not SocketExists(lngIndex) Then Exit Function

Dim udtSockAddr As sockaddr_in

With udtSockAddr
    .sin_addr = lngAddressInternal
    .sin_family = AF_INET
    .sin_port = api_htons(modSocketPlus.UnsignedToInteger(lngLocalPortInternal))
End With

'bind the socket
lngResult = api_bind(m_Socket(lngIndex).lngSocketHandle, udtSockAddr, LenB(udtSockAddr))

If lngResult = SOCKET_ERROR Then

    lngErrorCode = Err.LastDllError
    Err.Raise lngErrorCode, "clsSocketPlus.BindInternal", GetErrorDescription(lngErrorCode)
    
Else

    If lngLocalPortInternal <> 0 Then
    
        Debug.Print "OK Bind HOST: " & strLocalHostInternal & " PORT: " & lngLocalPortInternal
        m_Socket(lngIndex).lngLocalPort = lngLocalPortInternal
        
    Else
        lngResult = GetLocalPort(m_Socket(lngIndex).lngSocketHandle)
        
        If lngResult = SOCKET_ERROR Then
            lngErrorCode = Err.LastDllError
            Err.Raise lngErrorCode, "clsSocketPlus.BindInternal", GetErrorDescription(lngErrorCode)
        Else
            Debug.Print "OK Bind HOST: " & strLocalHostInternal & " PORT: " & lngResult
            m_Socket(lngIndex).lngLocalPortBind = lngResult
        End If
        
    End If
    
    BindInternal = True
End If
End Function

'Allocate some memory for HOSTEN structure and returns
'a pointer to this buffer if no error occurs.
'Returns 0 if it fails.
Private Function AllocateMemory(ByVal lngIndex As Long) As Long
With m_Socket(lngIndex)
    .lngMemoryHandle = api_GlobalAlloc(GMEM_FIXED, MAXGETHOSTSTRUCT)

    If .lngMemoryHandle <> 0 Then
        .lngMemoryPointer = api_GlobalLock(.lngMemoryHandle)
    
        If .lngMemoryPointer <> 0 Then
            api_GlobalUnlock (.lngMemoryHandle)
            AllocateMemory = .lngMemoryPointer
        Else
            api_GlobalFree (.lngMemoryHandle)
            AllocateMemory = .lngMemoryPointer '0
        End If

    Else
        AllocateMemory = m_Socket(lngIndex).lngMemoryHandle '0
    End If
End With
End Function

'Free memory allocated by AllocateMemory
Private Sub FreeMemory(ByVal lngIndex As Long)
With m_Socket(lngIndex)
    If .lngMemoryHandle <> 0 Then
        .lngMemoryPointer = 0
        api_GlobalFree .lngMemoryHandle
        .lngMemoryHandle = 0
        Debug.Print "OK Freed resolution memory"
    End If
End With
End Sub

Private Function GetLocalHostName() As String
Dim strHostNameBuf As String * LOCAL_HOST_BUFF
Dim lngResult As Long

lngResult = api_gethostname(strHostNameBuf, LOCAL_HOST_BUFF)

If lngResult = SOCKET_ERROR Then
    GetLocalHostName = vbNullString
    Dim lngErrorCode As Long
    lngErrorCode = Err.LastDllError
    Err.Raise lngErrorCode, "clsSocketPlus.GetLocalHostName", GetErrorDescription(lngErrorCode)
Else
    GetLocalHostName = Left(strHostNameBuf, InStr(1, strHostNameBuf, vbNullChar) - 1)
End If
End Function

'Get local IP when the socket isn't connected yet
Private Function GetLocalIP() As String
Dim lngResult As Long
Dim lngPtrToIP As Long
Dim strLocalHost As String
Dim arrIpAddress(1 To 4) As Byte
Dim Count As Integer
Dim udtHostent As HOSTENT
Dim strIpAddress As String

strLocalHost = GetLocalHostName

lngResult = api_gethostbyname(strLocalHost)

If lngResult = 0 Then
    GetLocalIP = vbNullString
    Dim lngErrorCode As Long
    lngErrorCode = Err.LastDllError
    Err.Raise lngErrorCode, "clsSocketPlus.GetLocalIP", GetErrorDescription(lngErrorCode)
Else
    api_CopyMemory udtHostent, ByVal lngResult, LenB(udtHostent)
    api_CopyMemory lngPtrToIP, ByVal udtHostent.hAddrList, 4
    api_CopyMemory arrIpAddress(1), ByVal lngPtrToIP, 4
   
    For Count = 1 To 4
        strIpAddress = strIpAddress & arrIpAddress(Count) & "."
    Next
   
    strIpAddress = Left$(strIpAddress, Len(strIpAddress) - 1)
    GetLocalIP = strIpAddress
End If
End Function

'If Host is an IP doesn't resolve anything and returns a
'a 32 bits long IP.
'If Host isn't an IP then returns vbNull, tries to resolve it
'in asynchronous way.
Private Function ResolveIfHostname(ByVal lngIndex As Long, ByVal Host As String) As Long
Dim lngAddress As Long
lngAddress = api_inet_addr(Host)

If lngAddress = INADDR_NONE Then 'if Host isn't an IP
    
    ResolveIfHostname = vbNull
    m_Socket(lngIndex).enmState = sckResolvingHost: Debug.Print "STATE: sckResolvingHost"
    
    If AllocateMemory(lngIndex) Then
        
        Dim lngAsynHandle As Long
        lngAsynHandle = modSocketPlus.ResolveHost(Host, m_Socket(lngIndex).lngMemoryPointer, ObjPtr(Me))
        
        If lngAsynHandle = 0 Then
            FreeMemory lngIndex
            m_Socket(lngIndex).enmState = sckError: Debug.Print "STATE: sckError"
            Dim lngErrorCode As Long
            lngErrorCode = Err.LastDllError
            Dim blnCancelDisplay As Boolean
            blnCancelDisplay = True
            RaiseEvent Error(m_Socket(lngIndex).varUserIndex, lngErrorCode, GetErrorDescription(lngErrorCode), 0, "clsSocketPlus.ResolveIfHostname", "", 0, blnCancelDisplay)
            'If blnCancelDisplay = False Then MsgBox GetErrorDescription(lngErrorCode), vbOKOnly, "clsSocketPlus.ResolveIfHostname"
        Else
            m_Socket(lngIndex).colWaitingResolutions.Add lngAsynHandle, "R" & lngAsynHandle
            m_colWaitingResolutionsS.Add lngIndex, "R" & lngAsynHandle
            Debug.Print "Resolving host " & Host; " with handle " & lngAsynHandle
        End If
        
    Else
        
        m_Socket(lngIndex).enmState = sckError: Debug.Print "STATE: sckError"
        Debug.Print "Error trying to allocate memory"
        Err.Raise sckOutOfMemory, "clsSocketPlus.ResolveIfHostname", "Out of memory"
        
    End If
    
Else 'if Host is an IP doen't need to resolve anything
    ResolveIfHostname = lngAddress
End If
End Function

'Resolves a host (if necessary) in synchronous way
'If succeeds returns a 32 bits long IP,
'strHostIP = readable IP string and lngErrorCode = 0
'If fails returns vbNull,
'strHostIP = vbNullString and lngErrorCode <> 0
Private Function ResolveIfHostnameSync(ByVal Host As String, ByRef strHostIP As String, ByRef lngErrorCode As Long) As Long
Dim lngPtrToHOSTENT As Long
Dim udtHostent As HOSTENT
Dim lngAddress As Long
Dim lngPtrToIP As Long
Dim arrIpAddress(1 To 4) As Byte
Dim Count As Integer

lngAddress = api_inet_addr(Host)

If lngAddress = INADDR_NONE Then 'if Host isn't an IP
    
    lngPtrToHOSTENT = api_gethostbyname(Host)
    
    If lngPtrToHOSTENT = 0 Then
        lngErrorCode = Err.LastDllError
        strHostIP = vbNullString
        ResolveIfHostnameSync = vbNull
    Else
        api_CopyMemory udtHostent, ByVal lngPtrToHOSTENT, LenB(udtHostent)
        api_CopyMemory lngPtrToIP, ByVal udtHostent.hAddrList, 4
        api_CopyMemory arrIpAddress(1), ByVal lngPtrToIP, 4
        api_CopyMemory lngAddress, ByVal lngPtrToIP, 4
        
        For Count = 1 To 4
            strHostIP = strHostIP & arrIpAddress(Count) & "."
        Next
        
        strHostIP = Left$(strHostIP, Len(strHostIP) - 1)
        
        lngErrorCode = 0
        ResolveIfHostnameSync = lngAddress
    End If
    
Else 'if Host is an IP doen't need to resolve anything
    
    lngErrorCode = 0
    strHostIP = Host
    ResolveIfHostnameSync = lngAddress
    
End If
End Function

'Returns local port from a connected or bound socket.
'Returns SOCKET_ERROR if fails.
Private Function GetLocalPort(ByVal lngSocket As Long) As Long
Dim udtSockAddr As sockaddr_in
Dim lngResult As Long

lngResult = api_getsockname(lngSocket, udtSockAddr, LenB(udtSockAddr))

If lngResult = SOCKET_ERROR Then
    GetLocalPort = SOCKET_ERROR
Else
    GetLocalPort = modSocketPlus.IntegerToUnsigned(api_ntohs(udtSockAddr.sin_port))
End If
End Function


'Check which protocol we are using to decide which
'function should handle the data sending.
Private Sub SendBufferedData(ByVal lngIndex As Long)
If m_Socket(lngIndex).enmProtocol = sckTCPProtocol Then
    SendBufferedDataTCP lngIndex
Else
    SendBufferedDataUDP lngIndex
End If
End Sub

'Send buffered data if we are using UDP protocol.
Private Sub SendBufferedDataUDP(ByVal lngIndex As Long)
Dim lngAddress As Long
Dim udtSockAddr As sockaddr_in
Dim arrData() As Byte
Dim lngBufferLength As Long
Dim lngResult As Long
Dim lngErrorCode As Long

   
Dim strTemp As String
lngAddress = ResolveIfHostnameSync(m_Socket(lngIndex).strRemoteHost, strTemp, lngErrorCode)
    
If lngErrorCode <> 0 Then
    m_Socket(lngIndex).strSendBuffer = ""
    
    If lngErrorCode = WSAEAFNOSUPPORT Then
        Err.Raise lngErrorCode, "clsSocketPlus.SendBufferedDataUDP", GetErrorDescription(lngErrorCode)
    Else
        Err.Raise sckInvalidArg, "clsSocketPlus.SendBufferedDataUDP", "Invalid argument"
    End If
End If

With udtSockAddr
    .sin_addr = lngAddress
    .sin_family = AF_INET
    .sin_port = api_htons(modSocketPlus.UnsignedToInteger(m_Socket(lngIndex).lngRemotePort))
End With
    
lngBufferLength = Len(m_Socket(lngIndex).strSendBuffer)
    
arrData() = StrConv(m_Socket(lngIndex).strSendBuffer, vbFromUnicode)
    
m_Socket(lngIndex).strSendBuffer = ""

lngResult = api_sendto(m_Socket(lngIndex).lngSocketHandle, arrData(0), lngBufferLength, 0&, udtSockAddr, LenB(udtSockAddr))
    
If lngResult = SOCKET_ERROR Then
    lngErrorCode = Err.LastDllError
    m_Socket(lngIndex).enmState = sckError: Debug.Print "STATE: sckError"
    Dim blnCancelDisplay As Boolean
    blnCancelDisplay = True
    RaiseEvent Error(m_Socket(lngIndex).varUserIndex, lngErrorCode, GetErrorDescription(lngErrorCode), 0, "clsSocketPlus.SendBufferedDataUDP", "", 0, blnCancelDisplay)
    'If blnCancelDisplay = False Then MsgBox GetErrorDescription(lngErrorCode), vbOKOnly, "clsSocketPlus.SendBufferedDataUDP"
End If
    
End Sub

'Send buffered data if we are using TCP protocol.
Private Sub SendBufferedDataTCP(ByVal lngIndex As Long)

Dim arrData()       As Byte
Dim lngBufferLength As Long
Dim lngResult    As Long
Dim lngTotalSent As Long

With m_Socket(lngIndex)

    Do Until lngResult = SOCKET_ERROR Or Len(.strSendBuffer) = 0

        lngBufferLength = Len(.strSendBuffer)

        If lngBufferLength > .lngSendBufferLen Then
            lngBufferLength = .lngSendBufferLen
            arrData() = StrConv(Left$(.strSendBuffer, .lngSendBufferLen), vbFromUnicode)
        Else
            arrData() = StrConv(.strSendBuffer, vbFromUnicode)
        End If

        lngResult = api_send(.lngSocketHandle, arrData(0), lngBufferLength, 0&)

        If lngResult = SOCKET_ERROR Then
            Dim lngErrorCode As Long
            lngErrorCode = Err.LastDllError
    
            If lngErrorCode = WSAEWOULDBLOCK Then
                Debug.Print "WARNING: Send buffer full, waiting..."
                If lngTotalSent > 0 Then RaiseEvent SendProgress(.varUserIndex, lngTotalSent, Len(.strSendBuffer))
            Else
                .enmState = sckError: Debug.Print "STATE: sckError"
                Dim blnCancelDisplay As Boolean
                blnCancelDisplay = True
                RaiseEvent Error(.varUserIndex, lngErrorCode, GetErrorDescription(lngErrorCode), 0, "clsSocketPlus.SendBufferedData", "", 0, blnCancelDisplay)
                'If blnCancelDisplay = False Then MsgBox GetErrorDescription(lngErrorCode), vbOKOnly, "clsSocketPlus.SendBufferedData"
            End If

        Else
            Debug.Print "OK Bytes sent: " & lngResult
            lngTotalSent = lngTotalSent + lngResult
            If Len(.strSendBuffer) > lngResult Then
                .strSendBuffer = Mid$(.strSendBuffer, lngResult + 1)
            Else
                Debug.Print "OK Finished SENDING"
                .strSendBuffer = ""
                Dim lngTemp As Long
                lngTemp = lngTotalSent
                lngTotalSent = 0
                RaiseEvent SendProgress(.varUserIndex, lngTemp, 0)
                RaiseEvent SendComplete(.varUserIndex)
            End If
        End If

    Loop
    
End With

End Sub

'This function retrieves data from the Winsock buffer
'into the class local buffer. The function returns number
'of bytes retrieved (received).
Private Function RecvDataToBuffer(ByVal lngIndex As Long) As Long
Dim arrBuffer() As Byte
Dim lngBytesReceived As Long
Dim strBuffTemporal As String

With m_Socket(lngIndex)

    ReDim arrBuffer(.lngRecvBufferLen - 1)

    lngBytesReceived = api_recv(.lngSocketHandle, arrBuffer(0), .lngRecvBufferLen, 0&)

    If lngBytesReceived = SOCKET_ERROR Then
    
        .enmState = sckError: Debug.Print "STATE: sckError"
        Dim lngErrorCode As Long
        lngErrorCode = Err.LastDllError
        Err.Raise lngErrorCode, "clsSocketPlus.RecvDataToBuffer", GetErrorDescription(lngErrorCode)
    
    ElseIf lngBytesReceived > 0 Then
    
        strBuffTemporal = StrConv(arrBuffer(), vbUnicode)
        .strRecvBuffer = .strRecvBuffer & Left$(strBuffTemporal, lngBytesReceived)
        RecvDataToBuffer = lngBytesReceived
    
    End If

End With
End Function

'Retrieves some socket options.
'If it is an UDP socket also sets SO_BROADCAST option.
Private Sub ProcessOptions(ByVal lngIndex As Long)
Dim lngResult As Long
Dim lngBuffer As Long
Dim lngErrorCode As Long

With m_Socket(lngIndex)

    If .enmProtocol = sckTCPProtocol Then
        lngResult = api_getsockopt(.lngSocketHandle, SOL_SOCKET, SO_RCVBUF, lngBuffer, LenB(lngBuffer))
    
        If lngResult = SOCKET_ERROR Then
            lngErrorCode = Err.LastDllError
            Err.Raise lngErrorCode, "clsSocketPlus.ProcessOptions", GetErrorDescription(lngErrorCode)
        Else
            .lngRecvBufferLen = lngBuffer
        End If

        lngResult = api_getsockopt(.lngSocketHandle, SOL_SOCKET, SO_SNDBUF, lngBuffer, LenB(lngBuffer))

        If lngResult = SOCKET_ERROR Then
            lngErrorCode = Err.LastDllError
            Err.Raise lngErrorCode, "clsSocketPlus.ProcessOptions", GetErrorDescription(lngErrorCode)
        Else
            .lngSendBufferLen = lngBuffer
        End If

    Else
        lngBuffer = 1
        lngResult = api_setsockopt(.lngSocketHandle, SOL_SOCKET, SO_BROADCAST, lngBuffer, LenB(lngBuffer))
    
        lngResult = api_getsockopt(.lngSocketHandle, SOL_SOCKET, SO_MAX_MSG_SIZE, lngBuffer, LenB(lngBuffer))

        If lngResult = SOCKET_ERROR Then
            lngErrorCode = Err.LastDllError
            Err.Raise lngErrorCode, "clsSocketPlus.ProcessOptions", GetErrorDescription(lngErrorCode)
        Else
            .lngRecvBufferLen = lngBuffer
            .lngSendBufferLen = lngBuffer
        End If
    End If


    Debug.Print "Winsock buffer size for sends: " & .lngRecvBufferLen
    Debug.Print "Winsock buffer size for receives: " & .lngSendBufferLen
    
End With
End Sub


'This function is to retrieve data from the buffer. If we are using TCP
'then the data is retrieved from a local buffer (m_strRecvBuffer). If we
'are using UDP the data is retrieved from winsock buffer.
'It can be called by two public methods of the class - GetData and PeekData.
'Behavior of the function is defined by the blnPeek argument. If a value of
'that argument is TRUE, the function returns number of bytes in the
'buffer, and copy data from that buffer into the data argument.
'If a value of the blnPeek is FALSE, then this function returns number of
'bytes received, and move data from the buffer into the data
'argument. MOVE means that data will be removed from the buffer.
Private Function RecvData(ByVal lngIndex As Long, ByRef Data As Variant, ByVal blnPeek As Boolean, Optional varClass As Variant, Optional maxLen As Variant) As Long

Dim blnMaxLenMiss   As Boolean
Dim blnClassMiss    As Boolean
Dim strRecvData     As String
Dim lngBufferLen    As Long
Dim arrBuffer()     As Byte
Dim lngErrorCode    As Long

If m_Socket(lngIndex).enmProtocol = sckTCPProtocol Then
    lngBufferLen = Len(m_Socket(lngIndex).strRecvBuffer)
Else
    lngBufferLen = GetBufferLenUDP(lngIndex)
End If

blnMaxLenMiss = IsMissing(maxLen)
blnClassMiss = IsMissing(varClass)

'Select type of data
If varType(Data) = vbEmpty Then
    If blnClassMiss Then varClass = vbArray + vbByte
Else
    varClass = varType(Data)
End If

'As stated on Winsock control documentation if the
'data type passed is string or byte array type then
'we must take into account maxLen argument.
'If it is another type maxLen is ignored.
If varClass = vbString Or varClass = vbArray + vbByte Then

    If blnMaxLenMiss Then 'if maxLen argument is missing
    
        If lngBufferLen = 0 Then
        
            RecvData = 0
        
            arrBuffer = StrConv("", vbFromUnicode)
            Data = arrBuffer

            Exit Function
    
        Else
            
            RecvData = lngBufferLen
            BuildArray lngIndex, lngBufferLen, blnPeek, lngErrorCode, arrBuffer

        End If

    Else 'if maxLen argument is not missing

        If maxLen = 0 Or lngBufferLen = 0 Then

            RecvData = 0
        
            arrBuffer = StrConv("", vbFromUnicode)
            Data = arrBuffer
            
            If m_Socket(lngIndex).enmProtocol = sckUDPProtocol Then
                EmptyBuffer lngIndex
                Err.Raise WSAEMSGSIZE, "clsSocketPlus.RecvData", GetErrorDescription(WSAEMSGSIZE)
            End If
            
            Exit Function
        
        ElseIf maxLen > lngBufferLen Then
            
            RecvData = lngBufferLen
            BuildArray lngIndex, lngBufferLen, blnPeek, lngErrorCode, arrBuffer

        Else
            
            RecvData = CLng(maxLen)
            BuildArray lngIndex, CLng(maxLen), blnPeek, lngErrorCode, arrBuffer

        End If
    
    End If
        
End If

    Select Case varClass
    
    Case vbString
        Dim strData As String
        strData = StrConv(arrBuffer(), vbUnicode)
        Data = strData
    Case vbArray + vbByte
        Data = arrBuffer
    Case vbBoolean
        Dim blnData As Boolean
        If LenB(blnData) > lngBufferLen Then Exit Function
        BuildArray lngIndex, LenB(blnData), blnPeek, lngErrorCode, arrBuffer
        RecvData = LenB(blnData)
        api_CopyMemory blnData, arrBuffer(0), LenB(blnData)
        Data = blnData
    Case vbByte
        Dim bytData As Byte
        If LenB(bytData) > lngBufferLen Then Exit Function
        BuildArray lngIndex, LenB(bytData), blnPeek, lngErrorCode, arrBuffer
        RecvData = LenB(bytData)
        api_CopyMemory bytData, arrBuffer(0), LenB(bytData)
        Data = bytData
    Case vbCurrency
        Dim curData As Currency
        If LenB(curData) > lngBufferLen Then Exit Function
        BuildArray lngIndex, LenB(curData), blnPeek, lngErrorCode, arrBuffer
        RecvData = LenB(curData)
        api_CopyMemory curData, arrBuffer(0), LenB(curData)
        Data = curData
    Case vbDate
        Dim datData As Date
        If LenB(datData) > lngBufferLen Then Exit Function
        BuildArray lngIndex, LenB(datData), blnPeek, lngErrorCode, arrBuffer
        RecvData = LenB(datData)
        api_CopyMemory datData, arrBuffer(0), LenB(datData)
        Data = datData
    Case vbDouble
        Dim dblData As Double
        If LenB(dblData) > lngBufferLen Then Exit Function
        BuildArray lngIndex, LenB(dblData), blnPeek, lngErrorCode, arrBuffer
        RecvData = LenB(dblData)
        api_CopyMemory dblData, arrBuffer(0), LenB(dblData)
        Data = dblData
    Case vbInteger
        Dim intData As Integer
        If LenB(intData) > lngBufferLen Then Exit Function
        BuildArray lngIndex, LenB(intData), blnPeek, lngErrorCode, arrBuffer
        RecvData = LenB(intData)
        api_CopyMemory intData, arrBuffer(0), LenB(intData)
        Data = intData
    Case vbLong
        Dim lngData As Long
        If LenB(lngData) > lngBufferLen Then Exit Function
        BuildArray lngIndex, LenB(lngData), blnPeek, lngErrorCode, arrBuffer
        RecvData = LenB(lngData)
        api_CopyMemory lngData, arrBuffer(0), LenB(lngData)
        Data = lngData
    Case vbSingle
        Dim sngData As Single
        If LenB(sngData) > lngBufferLen Then Exit Function
        BuildArray lngIndex, LenB(sngData), blnPeek, lngErrorCode, arrBuffer
        RecvData = LenB(sngData)
        api_CopyMemory sngData, arrBuffer(0), LenB(sngData)
        Data = sngData
    Case Else
        Err.Raise sckUnsupported, "clsSocketPlus.RecvData", "Unsupported variant type."
       
    End Select

'if BuildArray returns an error is handled here
If lngErrorCode <> 0 Then
    Err.Raise lngErrorCode, "clsSocketPlus.RecvData", GetErrorDescription(lngErrorCode)
End If

End Function

'Returns a byte array of Size bytes filled with incoming buffer data.
Private Sub BuildArray(ByVal lngIndex As Long, ByVal Size As Long, ByVal blnPeek As Boolean, ByRef lngErrorCode As Long, ByRef bytArray() As Byte)
Dim strData As String

With m_Socket(lngIndex)
    
    If .enmProtocol = sckTCPProtocol Then
        
        strData = Left$(.strRecvBuffer, CLng(Size))
        bytArray = StrConv(strData, vbFromUnicode)
                
        If Not blnPeek Then
            .strRecvBuffer = Mid$(.strRecvBuffer, Size + 1)
        End If

    Else 'UDP protocol
        Dim arrBuffer() As Byte
        Dim lngResult As Long
        Dim udtSockAddr As sockaddr_in
        Dim lngFlags As Long
    
        If blnPeek Then lngFlags = MSG_PEEK
    
        ReDim arrBuffer(Size - 1)
    
        lngResult = api_recvfrom(.lngSocketHandle, arrBuffer(0), Size, lngFlags, udtSockAddr, LenB(udtSockAddr))
    
        If lngResult = SOCKET_ERROR Then
            lngErrorCode = Err.LastDllError
        End If
    
        bytArray = arrBuffer
        GetRemoteInfoFromSI udtSockAddr, .lngRemotePort, .strRemoteHostIP, .strRemoteHost
    
    End If
End With
End Sub

'Clean resolution system that is in charge of
'asynchronous hostname resolutions.
Private Sub CleanResolutionSystem(ByVal lngIndex As Long)
Dim varAsynHandle As Variant
Dim lngResult As Long

'cancel async resolutions if they're still running
For Each varAsynHandle In m_Socket(lngIndex).colWaitingResolutions
    lngResult = api_WSACancelAsyncRequest(varAsynHandle)
    If lngResult = 0 Then
        modSocketPlus.UnregisterResolution varAsynHandle
        
        Set m_Socket(lngIndex).colWaitingResolutions = Nothing
        Set m_Socket(lngIndex).colWaitingResolutions = New Collection

        'free memory buffer where resolution results are stored
        FreeMemory lngIndex
        
    End If
Next

End Sub

'Retrieves local info from a connected socket.
'If succeeds returns TRUE and loads the arguments.
'If fails returns FALSE and arguments are not loaded.
Private Function GetLocalInfo(ByVal lngSocket As Long, ByRef lngLocalPort As Long, ByRef strLocalIP As String) As Boolean
GetLocalInfo = False
Dim lngResult As Long
Dim udtSockAddr As sockaddr_in

lngResult = api_getsockname(lngSocket, udtSockAddr, LenB(udtSockAddr))

If lngResult = SOCKET_ERROR Then
    lngLocalPort = 0
    strLocalIP = ""
Else
    GetLocalInfo = True
    lngLocalPort = IntegerToUnsigned(api_ntohs(udtSockAddr.sin_port))
    strLocalIP = StringFromPointer(api_inet_ntoa(udtSockAddr.sin_addr))
End If

End Function

'Retrieves remote info from a connected socket.
'If succeeds returns TRUE and loads the arguments.
'If fails returns FALSE and arguments are not loaded.
Private Function GetRemoteInfo(ByVal lngSocket As Long, ByRef lngRemotePort As Long, ByRef strRemoteHostIP As String, ByRef strRemoteHost As String) As Boolean
GetRemoteInfo = False
Dim lngResult As Long
Dim udtSockAddr As sockaddr_in

lngResult = api_getpeername(lngSocket, udtSockAddr, LenB(udtSockAddr))

If lngResult = 0 Then
    GetRemoteInfo = True
    GetRemoteInfoFromSI udtSockAddr, lngRemotePort, strRemoteHostIP, strRemoteHost
Else
   lngRemotePort = 0
   strRemoteHostIP = ""
   strRemoteHost = ""
End If
End Function

'Gets remote info from a sockaddr_in structure.
Private Sub GetRemoteInfoFromSI(ByRef udtSockAddr As sockaddr_in, ByRef lngRemotePort As Long, ByRef strRemoteHostIP As String, ByRef strRemoteHost As String)

'Dim lngResult As Long
'Dim udtHostent As HOSTENT

lngRemotePort = IntegerToUnsigned(api_ntohs(udtSockAddr.sin_port))
strRemoteHostIP = StringFromPointer(api_inet_ntoa(udtSockAddr.sin_addr))
'lngResult = api_gethostbyaddr(udtSockAddr.sin_addr, 4&, AF_INET)

'If lngResult <> 0 Then
'    api_CopyMemory udtHostent, ByVal lngResult, LenB(udtHostent)
'    strRemoteHost = StringFromPointer(udtHostent.hName)
'Else
    strRemoteHost = ""
'End If

End Sub

'Returns winsock incoming buffer length from an UDP socket.
Private Function GetBufferLenUDP(ByVal lngIndex As Long) As Long
Dim lngResult As Long
Dim lngBuffer As Long
lngResult = api_ioctlsocket(m_Socket(lngIndex).lngSocketHandle, FIONREAD, lngBuffer)

If lngResult = SOCKET_ERROR Then
    GetBufferLenUDP = 0
Else
    GetBufferLenUDP = lngBuffer
End If
End Function

'Empty winsock incoming buffer from an UDP socket.
Private Sub EmptyBuffer(ByVal lngIndex As Long)
Dim b As Byte
api_recv m_Socket(lngIndex).lngSocketHandle, b, Len(b), 0&
End Sub


'Removes the socket Index from the socket array
'If it is the last one cleans the array
Private Sub CleanSocketArray(ByVal lngIndex As Long)

On Error GoTo Error_Handler

Dim lngNextEmptyIndex As Long
lngNextEmptyIndex = UBound(m_EmptyIndex) + 1

If UBound(m_Socket) = lngNextEmptyIndex Then
    ReDim m_Socket(0) As typSocket
    ReDim m_EmptyIndex(0) As Long
Else 'if it is the last in the array we can erase it from the array
    If UBound(m_Socket) = lngIndex Then
        ReDim Preserve m_Socket(0 To lngIndex - 1) As typSocket
    Else 'if it isn't the last in the array we can't erase it
        With m_Socket(lngIndex)
        Set .colWaitingResolutions = Nothing
        Set .colWaitingResolutions = New Collection
        .enmProtocol = sckTCPProtocol
        .enmState = sckClosed
        .lngLocalPort = -1
        .lngLocalPortBind = -1
        .lngMemoryHandle = 0
        .lngMemoryPointer = 0
        .lngRecvBufferLen = 0
        .lngRemotePort = -1
        .lngSendBufferLen = 0
        .lngSocketHandle = -1
        .strLocalIP = ""
        .strRecvBuffer = ""
        .strRemoteHost = ""
        .strRemoteHostIP = ""
        .strSendBuffer = ""
        '.strTag = ""
        .varUserIndex = 0
        End With
        ReDim Preserve m_EmptyIndex(lngNextEmptyIndex)
        m_EmptyIndex(lngNextEmptyIndex) = lngIndex
    End If
End If

Exit Sub

Error_Handler:
    Err.Raise sckOutOfMemory, "clsSocketPlus.CleanSocketArray", "Out of memory"
End Sub


'Returns a free index to be used on the socket array.
'Redims the array if necessary.
Private Function GetFreeSocketIndex() As Long
On Error GoTo Error_Handler:

Dim lngFreeBound As Long
Dim lngIndex As Long

lngFreeBound = UBound(m_EmptyIndex)

If lngFreeBound = 0 Then    'if there isn't any empty index

    lngIndex = UBound(m_Socket) + 1
    ReDim Preserve m_Socket(lngIndex)
    
Else 'if there are some empty indexes we take the last one
    lngIndex = m_EmptyIndex(UBound(m_EmptyIndex))
    ReDim Preserve m_EmptyIndex(UBound(m_EmptyIndex) - 1)

End If

GetFreeSocketIndex = lngIndex

Exit Function

Error_Handler:
    Err.Raise sckOutOfMemory, "clsSocketPlus.GetFreeSocketIndex", "Out of memory"
End Function

'Get socket array index from user index or string index
Private Function GetSocketIndex(Index As Variant) As Long
On Error GoTo Error_Handler

Select Case varType(Index)
    Case vbString
        GetSocketIndex = m_colString_Index("I" & Index)
    Case vbInteger To vbLong
        GetSocketIndex = m_colUserIndex_Index("I" & Index)
    Case Else
        Err.Raise sckWrongIndex
End Select

Exit Function

Error_Handler:
    Dim lngErrorCode As Long
    Dim strErrorDesc As String
    
    lngErrorCode = Err.Number
    strErrorDesc = Err.Description
    
    Const strSource As String = "clsSocketPlus.GetSocketIndex"
    
    If Err.Number = sckWrongIndex Then
        Err.Raise sckWrongIndex, strSource, "Unsupported index type."
    ElseIf Err.Number = 5 Then
        Err.Raise sckMissingIndex, strSource, "Unknown index."
    Else
        Err.Raise lngErrorCode, strSource, strErrorDesc
    End If
End Function

'returns true if user index is registered on
'm_colUserIndex_Index
Private Function IsUserIndexRegistered(ByVal lngIndex As Long) As Boolean
On Error GoTo Error_Handler

m_colUserIndex_Index.Item ("I" & lngIndex)
IsUserIndexRegistered = True

Exit Function

Error_Handler:
    IsUserIndexRegistered = False
End Function
