/** 
 * @file    kNetwork.cpp
 *
 * @internal
 * Copyright (C) 2008-2022 by LMI Technologies Inc.
 * Licensed under the MIT License.
 * Redistributed files must retain the above copyright notice.
 */
#define K_PLATFORM
#include <kApi/Io/kNetwork.h>

#include <kApi/Data/kArrayList.h>
#include <kApi/Data/kMap.h>
#include <kApi/Io/kSocket.h>
#include <kApi/Threads/kLock.h>
#include <kApi/Threads/kThread.h>
#include <kApi/Threads/kSemaphore.h>
#include <kApi/Utils/kEvent.h>

#include <stdio.h>

/* 
 * kIpVersion enum
 */
kBeginEnumEx(k, kIpVersion)
    kAddEnumerator(kIpVersion, kIP_VERSION_4)
kEndEnumEx()

/* 
 * kIpAddress structure
 */

kBeginValueEx(k, kIpAddress)
    kAddField(kIpAddress, kIpVersion, version)
    kAddField(kIpAddress, kByte, address)

    kAddPrivateVMethod(kIpAddress, kValue, VEquals)
    kAddPrivateVMethod(kIpAddress, kValue, VHashCode)
kEndValueEx()

kFx(kBool) xkIpAddress_VEquals(kType type, const void* value, const void* other)
{
    const kIpAddress* a = (const kIpAddress*) value; 
    const kIpAddress* b = (const kIpAddress*) other; 

    if (a->version == b->version)
    {
        if (a->version == kIP_VERSION_4)
        {
            return kMemEquals(a->address, b->address, 4); 
        }
    }

    return kFALSE;
}

kFx(kSize) xkIpAddress_VHashCode(kType type, const void* value)
{
    const kIpAddress* address = (const kIpAddress*) value; 

    if (address->version == kIP_VERSION_4)
    {
        return xkHashBytes(address->address, 4); 
    }
    else
    {
        return xkHashBytes(address->address, sizeof(address->address)); 
    }
}

kFx(kIpAddress) kIpAddress_Any(kIpVersion version)
{    
    kAssert(version == kIP_VERSION_4); 

    return kIpAddress_AnyV4(); 
}

kFx(kIpAddress) kIpAddress_AnyV4()
{
    kIpAddress address = { kIP_VERSION_4 }; 

    return address; 
}

kFx(kIpAddress) kIpAddress_BroadcastV4()
{   
    kIpAddress address = { kIP_VERSION_4 }; 

    address.address[0] = 0xFF; 
    address.address[1] = 0xFF; 
    address.address[2] = 0xFF; 
    address.address[3] = 0xFF; 

    return address;   
}

kFx(kIpAddress) kIpAddress_Loopback(kIpVersion version)
{
    kAssert(version == kIP_VERSION_4); 

    return kIpAddress_LoopbackV4(); 
}

kFx(kIpAddress) kIpAddress_LoopbackV4()
{
    kIpAddress address = { kIP_VERSION_4 }; 

    address.address[0] = 0x7F; 
    address.address[1] = 0x00; 
    address.address[2] = 0x00; 
    address.address[3] = 0x01; 

    return address; 
}

kFx(kStatus) xkIpAddress_ToSockAddr(kIpAddress address, k32u port, struct sockaddr_in *sockAddr)
{
    kMemSet(sockAddr, 0, sizeof(struct sockaddr_in));

    sockAddr->sin_family = AF_INET;
    sockAddr->sin_addr.s_addr = kIpAddress_ToNet32u(address); 
    sockAddr->sin_port = htons((k16u)port); 

    return kOK;
}

kFx(kStatus) xkIpAddress_FromSockAddr(struct sockaddr_in* sockAddr, kIpAddress *address, k32u *port)
{   
    if (!kIsNull(address))
    {
        *address = kIpAddress_FromNet32u(sockAddr->sin_addr.s_addr); 
    }

    if (!kIsNull(port))
    {
        *port =  htons(sockAddr->sin_port); 
    }

    return kOK;
}

kFx(kStatus) kIpAddress_Parse(kIpAddress* address, const kChar* text)
{
    k32u a, b, c, d;

    if ((sscanf(text, "%u.%u.%u.%u", &a, &b, &c, &d) != 4) || 
        (a > k8U_MAX) || (b > k8U_MAX) || (c > k8U_MAX) || (d > k8U_MAX))
    {
        return kERROR_PARAMETER; 
    }

    kItemZero(address, sizeof(kIpAddress)); 

    address->version = kIP_VERSION_4; 

    address->address[0] = (kByte) a; 
    address->address[1] = (kByte) b; 
    address->address[2] = (kByte) c; 
    address->address[3] = (kByte) d; 
     
    return kOK;
}

kFx(kStatus) kIpAddress_Format(kIpAddress address, kChar* text, kSize capacity)
{        
    kCheckArgs(address.version == kIP_VERSION_4); 

    kStrPrintf(text, capacity, "%u.%u.%u.%u", address.address[0], address.address[1], address.address[2], address.address[3]); 
    
    return kOK;
}

kFx(kBool) kIpAddress_Equals(kIpAddress a, kIpAddress b)
{
    if (a.version == kIP_VERSION_4)
    {
        return (b.version == a.version) && kMemEquals(&a.address[0], &b.address[0], 4); 
    }
    else
    {
        return kFALSE; 
    }    
}

kFx(kBool) kIpAddress_IsLoopback(kIpAddress address)
{
    return kIpAddress_Equals(address, kIpAddress_Loopback(address.version)); 
}

kFx(kBool) kIpAddress_IsLinkLocal(kIpAddress address)
{
    if (address.version == kIP_VERSION_4)
    {
        return (address.address[0] == 169) && (address.address[1] == 254);
    }
    else
    {
        return kFALSE; 
    }   
}

kFx(k32u) kIpAddress_ToHost32u(kIpAddress address)
{
    kAssert(address.version == kIP_VERSION_4); 

    return (k32u) ((address.address[0] << 24) | (address.address[1] << 16) | (address.address[2] << 8) | (address.address[3])); 
}

kFx(k32u) kIpAddress_ToNet32u(kIpAddress address)
{
    kAssert(address.version == kIP_VERSION_4); 

    return (k32u) ((address.address[3] << 24) | (address.address[2] << 16) | (address.address[1] << 8) | (address.address[0])); 
}

kFx(kIpAddress) kIpAddress_FromHost32u(k32u address)
{
    kIpAddress out = { kIP_VERSION_4 }; 

    out.address[0] = (k8u) ((address >> 24) & 0xFF);
    out.address[1] = (k8u) ((address >> 16) & 0xFF);
    out.address[2] = (k8u) ((address >>  8) & 0xFF);
    out.address[3] = (k8u) (address & 0xFF);

    return out; 
}

kFx(kIpAddress) kIpAddress_FromNet32u(k32u address)
{
    kIpAddress out = { kIP_VERSION_4 }; 

    out.address[3] = (k8u) ((address >> 24) & 0xFF);
    out.address[2] = (k8u) ((address >> 16) & 0xFF);
    out.address[1] = (k8u) ((address >>  8) & 0xFF);
    out.address[0] = (k8u) (address & 0xFF);

    return out; 
}

kFx(kIpAddress) kIpAddress_Network(kIpAddress address, k32u prefixLength)
{
    kAssert(address.version = kIP_VERSION_4);

    k32u mask = xkIpAddress_SubnetPrefixToMask(prefixLength);

    return kIpAddress_FromHost32u(kIpAddress_ToHost32u(address) & mask);
}

kFx(kBool) kIpAddress_NetworkEquals(kIpAddress a, kIpAddress b, k32u prefixLength) 
{
    return kIpAddress_Equals(kIpAddress_Network(a, prefixLength), kIpAddress_Network(b, prefixLength));
}

kFx(k32u) xkIpAddress_SubnetPrefixToMask(k32u prefix)
{
    k32u mask = 0; 
    k32s lower = 32 - (k32s) prefix; 
    k32s i = 0; 

    for (i = 31; i >= lower; --i)
    {
        mask |= (1 << i); 
    }

    return mask; 
}

kFx(k32u) xkIpAddress_SubnetMaskToPrefix(k32u mask)
{
    k32u i; 

    for (i = 0; i < 32; ++i)
    {
        if (mask & (1 << i))
        {
            return (32 - i); 
        }
    }

    return 0; 
}

/* 
 * kIpEndPoint structure
 */

kBeginValueEx(k, kIpEndPoint)
    kAddField(kIpEndPoint, kIpAddress, address)
    kAddField(kIpEndPoint, k32u, port)

    kAddPrivateVMethod(kIpEndPoint, kValue, VEquals)

kEndValueEx()

kFx(kBool) xkIpEndPoint_VEquals(kType type, const void* value, const void* other)
{
    const kIpEndPoint* a = (const kIpEndPoint*) value; 
    const kIpEndPoint* b = (const kIpEndPoint*) other; 

    return kValue_Equals(kTypeOf(kIpAddress), &a->address, &b->address) && (a->port == b->port); 
}

/*
 * [Deprecated] kIpEntry structure
 */
 
kBeginValueEx(k, kIpEntry)
    kAddField(kIpEntry, kIpAddress, address)
    kAddField(kIpEntry, kText128, name)
kEndValueEx()

/* 
 * kNetworkInterface class
 */
kBeginClassEx(k, kNetworkInterface)    
kEndClassEx()

kFx(kStatus) xkNetworkInterface_Construct(kNetworkInterface* iface, kNetworkAdapter adapter, kIpAddress address, k32u prefixLength, kAlloc allocator)
{
    kAlloc alloc = kAlloc_Fallback(allocator);
    kStatus status; 

    kCheck(kAlloc_GetObject(alloc, kTypeOf(kNetworkInterface), iface)); 

    if (!kSuccess(status = xkNetworkInterface_Init(*iface, kTypeOf(kNetworkInterface), adapter, address, prefixLength, alloc)))
    {
        kAlloc_FreeRef(alloc, iface); 
    }

    return status; 
}

kFx(kStatus) xkNetworkInterface_Init(kNetworkInterface iface, kType type, kNetworkAdapter adapter, kIpAddress address, k32u prefixLength, kAlloc alloc)
{
    kObjR(kNetworkInterface, iface); 

    kCheck(kObject_Init(iface, type, alloc)); 

    obj->adapter = adapter; 
    obj->address = address;
    obj->prefixLength = prefixLength;

    return kOK; 
}

/* 
 * kNetworkAdapter class
 */
kBeginClassEx(k, kNetworkAdapter)    
    kAddPrivateVMethod(kNetworkAdapter, kObject, VRelease)
kEndClassEx()

kFx(kStatus) xkNetworkAdapter_Construct(kNetworkAdapter* adapter, kNetworkInfo info, const kChar* name, kSize id, kBool isUp, kAlloc allocator)
{
    kAlloc alloc = kAlloc_Fallback(allocator);
    kStatus status; 

    kCheck(kAlloc_GetObject(alloc, kTypeOf(kNetworkAdapter), adapter)); 

    if (!kSuccess(status = xkNetworkAdapter_Init(*adapter, kTypeOf(kNetworkAdapter), info, name, id, isUp, alloc)))
    {
        kAlloc_FreeRef(alloc, adapter); 
    }

    return status; 
}

kFx(kStatus) xkNetworkAdapter_Init(kNetworkAdapter adapter, kType type, kNetworkInfo info, const kChar* name, kSize id, kBool isUp, kAlloc alloc)
{
    kObjR(kNetworkAdapter, adapter); 
    kStatus status = kOK; 

    kCheck(kObject_Init(adapter, type, alloc)); 

    obj->info = info; 
    obj->name = kNULL; 
    obj->id = id; 
    obj->isUp = isUp;
    obj->interfaces = kNULL;

    kTry
    {
        kTest(kString_Construct(&obj->name, name, alloc));

        kTest(kArrayList_Construct(&obj->interfaces, kTypeOf(kNetworkInterface), 0, alloc));
    }
    kCatch(&status); 
    {
        xkNetworkAdapter_VRelease(adapter); 
        kEndCatch(status); 
    }

    return kOK; 
}

kFx(kStatus) xkNetworkAdapter_VRelease(kNetworkAdapter adapter)
{
    kObj(kNetworkAdapter, adapter); 

    kCheck(kObject_Destroy(obj->name));
    kCheck(kObject_Dispose(obj->interfaces));

    kCheck(kObject_VRelease(adapter));

    return kOK;
}

kFx(kStatus) xkNetworkAdapter_AddInterface(kNetworkAdapter adapter, kIpAddress address, k32u prefixLength, kNetworkInterface* iface)
{    
    kObj(kNetworkAdapter, adapter); 
    kNetworkInterface newIface = kNULL;

    kTry
    {
        kTest(xkNetworkInterface_Construct(&newIface, adapter, address, prefixLength, kObject_Alloc(adapter)));

        kTest(kArrayList_Add(obj->interfaces, &newIface));

        if (!kIsNull(iface))
        {
            *iface = newIface;
        }

        newIface = kNULL;
    }
    kFinally
    {
        kDestroyRef(&newIface);

        kEndFinally();
    }

    return kOK;
}

/* 
 * kNetworkInfo class
 */
kBeginClassEx(k, kNetworkInfo)    
    kAddPrivateVMethod(kNetworkInfo, kObject, VRelease)
kEndClassEx()

kFx(kStatus) kNetworkInfo_Construct(kNetworkInfo* info, kAlloc allocator)
{
    kAlloc alloc = kAlloc_Fallback(allocator);
    kStatus status; 

    kCheck(kAlloc_GetObject(alloc, kTypeOf(kNetworkInfo), info)); 

    if (!kSuccess(status = xkNetworkInfo_Init(*info, kTypeOf(kNetworkInfo), alloc)))
    {
        kAlloc_FreeRef(alloc, info); 
    }

    return status; 
}

kFx(kStatus) xkNetworkInfo_Init(kNetworkInfo info, kType type, kAlloc alloc)
{
    kObjR(kNetworkInfo, info); 
    kStatus status = kOK; 

    kCheck(kObject_Init(info, type, alloc)); 

    obj->adapters = kNULL; 
    obj->interfaces = kNULL;

    kTry
    {
        kTest(kArrayList_Construct(&obj->adapters, kTypeOf(kNetworkAdapter), 0, alloc));
        kTest(kArrayList_Construct(&obj->interfaces, kTypeOf(kNetworkInterface), 0, alloc));

        kTest(kNetworkInfo_Refresh(info));
    }
    kCatch(&status); 
    {
        xkNetworkInfo_VRelease(info); 
        kEndCatch(status); 
    }

    return kOK; 
}

kFx(kStatus) xkNetworkInfo_VRelease(kNetworkInfo info)
{
    kObj(kNetworkInfo, info); 

    kCheck(kObject_Destroy(obj->interfaces));

    kCheck(kObject_Dispose(obj->adapters));

    kCheck(kObject_VRelease(info));

    return kOK;
}

kFx(kStatus) kNetworkInfo_Refresh(kNetworkInfo info)
{
    kObj(kNetworkInfo, info); 

    //clear existing data
    kCheck(kArrayList_Purge(obj->adapters));
    kCheck(kArrayList_Clear(obj->interfaces));

    //enumerate adapters
    kCheck(kApiLib_QueryNetInfoHandler()(info));

    //create flat list of interface references, for convenient access
    for (kSize i = 0; i < kNetworkInfo_AdapterCount(info); ++i)
    {
        kNetworkAdapter adapter = kNetworkInfo_AdapterAt(info, i); 
        
        for (kSize j = 0; j < kNetworkAdapter_InterfaceCount(adapter); ++j)
        {
            kNetworkInterface iface = kNetworkAdapter_InterfaceAt(adapter, j); 
 
            kCheck(kArrayList_AddT(obj->interfaces, &iface));
        }
    }

    return kOK;
}

kFx(kStatus) xkNetworkInfo_AddAdapter(kNetworkInfo info, const kChar* name, kSize id, kBool isUp, kNetworkAdapter* adapter)
{    
    kObj(kNetworkInfo, info); 
    kNetworkAdapter newAdapter = kNULL;

    kTry
    {
        kTest(xkNetworkAdapter_Construct(&newAdapter, adapter, name, id, isUp, kObject_Alloc(info)));

        kTest(kArrayList_Add(obj->adapters, &newAdapter));

        if (!kIsNull(adapter))
        {
            *adapter = newAdapter;
        }

        newAdapter = kNULL;
    }
    kFinally
    {
        kDestroyRef(&newAdapter);

        kEndFinally();
    }

    return kOK;
}

kFx(kStatus) kNetworkInfo_FindAdapterByName(kNetworkInfo info, const kChar* name, kNetworkAdapter* adapter)
{    
    kObj(kNetworkInfo, info); 

    for (kSize i = 0; i < kNetworkInfo_AdapterCount(info); ++i)
    {
        kNetworkAdapter adapterAt = kNetworkInfo_AdapterAt(info, i); 

        if (kStrEquals(kNetworkAdapter_Name(adapterAt), name))
        {
            if (!kIsNull(adapter))
            {
                *adapter = adapterAt;
            }
            return kOK;
        }
    }

    return kERROR_NOT_FOUND;
}

kFx(kStatus) kNetworkInfo_FindAdapterById(kNetworkInfo info, kSize id, kNetworkAdapter* adapter)
{    
    kObj(kNetworkInfo, info); 

    for (kSize i = 0; i < kNetworkInfo_AdapterCount(info); ++i)
    {
        kNetworkAdapter adapterAt = kNetworkInfo_AdapterAt(info, i); 

        if (kNetworkAdapter_Id(adapterAt) == id)
        {
            if (!kIsNull(adapter))
            {
                *adapter = adapterAt;
            }
            return kOK;
        }
    }

    return kERROR_NOT_FOUND;
} 

kFx(kStatus) kNetworkInfo_FindInterfaceByAddress(kNetworkInfo info, kIpAddress address, kNetworkInterface* iface)
{    
    kObj(kNetworkInfo, info); 

    for (kSize i = 0; i < kNetworkInfo_InterfaceCount(info); ++i)
    {
        kNetworkInterface interfaceAt = kNetworkInfo_InterfaceAt(info, i); 

        if (kIpAddress_Equals(kNetworkInterface_Address(interfaceAt), address))
        {
            if (!kIsNull(iface))
            {
                *iface = interfaceAt;
            }
            return kOK;
        }
    }

    return kERROR_NOT_FOUND;
} 

kFx(kStatus) kNetworkInfo_FindInterfaceByNetwork(kNetworkInfo info, kIpAddress address, kNetworkInterface* iface)
{    
    kObj(kNetworkInfo, info); 

    for (kSize i = 0; i < kNetworkInfo_InterfaceCount(info); ++i)
    {
        kNetworkInterface interfaceAt = kNetworkInfo_InterfaceAt(info, i); 
   
        if (kIpAddress_NetworkEquals(address, kNetworkInterface_Address(interfaceAt), kNetworkInterface_PrefixLength(interfaceAt)))
        {
            if (!kIsNull(iface))
            {
                *iface = interfaceAt;
            }
            return kOK;
        }
    }

    return kERROR_NOT_FOUND;
} 

/* 
 * kNetwork static class
 */
kBeginStaticClassEx(k, kNetwork)    
kEndStaticClassEx()

kFx(kStatus) xkNetwork_InitStatic()
{
    kStaticObj(kNetwork);

    kAtomic32s_Init(&sobj->shouldQuit, kFALSE); 
    sobj->eventLock = kNULL; 
    sobj->changeEvent = kNULL;
    sobj->changeThread = kNULL;

    kCheck(kLock_Construct(&sobj->eventLock, kNULL));
    kCheck(kEvent_Construct(&sobj->changeEvent, kNULL));

    kCheck(xkNetwork_InitStaticPlatform());
 
    return kOK; 
}

kFx(kStatus) xkNetwork_ReleaseStatic()
{    
    kStaticObj(kNetwork);

    kAtomic32s_Exchange(&sobj->shouldQuit, kTRUE);

    kCheck(kDestroyRef(&sobj->changeThread));

    kCheck(kDestroyRef(&sobj->changeEvent));
    kCheck(kDestroyRef(&sobj->eventLock));

    kCheck(xkNetwork_ReleaseStaticPlatform());

    return kOK; 
}

kFx(kStatus) kNetwork_AddChangeHandler(kCallbackFx function, kPointer receiver)
{
    kStaticObj(kNetwork);

    kLock_Enter(sobj->eventLock); 

    kTry
    {
        if (kIsNull(sobj->changeThread))
        {
            kAtomic32s_Exchange(&sobj->shouldQuit, kFALSE);

            kTest(kThread_Construct(&sobj->changeThread, kNULL)); 
            kTest(kThread_StartEx(sobj->changeThread, xkNetwork_ChangeThreadEntry, kNULL, 0, "kNetwork.Change", 0));
        }

        kTest(kEvent_Add(sobj->changeEvent, function, receiver));
    }
    kFinally
    {
        kLock_Exit(sobj->eventLock);

        kEndFinally();
    }

    return kOK;
}

kFx(kStatus) kNetwork_RemoveChangeHandler(kCallbackFx function, kPointer receiver)
{
    kStaticObj(kNetwork);

    kLock_Enter(sobj->eventLock); 

    kTry
    {
        kTest(kEvent_Remove(sobj->changeEvent, function, receiver));

        if (kEvent_Count(sobj->changeEvent) == 0)
        {
            kAtomic32s_Exchange(&sobj->shouldQuit, kTRUE);

            kTest(kDestroyRef(&sobj->changeThread));
        }
    }
    kFinally
    {
        kLock_Exit(sobj->eventLock);

        kEndFinally();
    }

    return kOK;
}

kFx(kStatus) xkNetwork_ChangeThreadEntry(kPointer context)
{
    kStaticObj(kNetwork);

    if (kSuccess(xkNetwork_OsBeginChangeNotification()))
    {
        while (!kAtomic32s_Get(&sobj->shouldQuit))
        {
            if (kApiLib_QueryNetChangeHandler()(xkNETWORK_QUIT_QUERY_PERIOD))
            {
                xkNetwork_NotifyChange();
            }
        }

        xkNetwork_OsEndChangeNotification(); 
    }

    return kOK;
}

kFx(kStatus) xkNetwork_NotifyChange()
{
    kStaticObj(kNetwork);

    kLock_Enter(sobj->eventLock); 

    kTry
    {
        kTest(kEvent_Notify(sobj->changeEvent, kNULL, kNULL));
    }
    kFinally
    {
        kLock_Exit(sobj->eventLock);

        kEndFinally();
    }

    return kOK;
}

kFx(kStatus) kNetwork_FindAdapterIdByName(const kChar* name, kSize* id)
{
    kNetworkInfo info = kNULL; 
    kNetworkAdapter adapter = kNULL;

    kTry
    {
        kTest(kNetworkInfo_Construct(&info, kNULL)); 
        
        kTest(kNetworkInfo_FindAdapterByName(info, name, &adapter)); 

        if (!kIsNull(id))
        {
            *id = kNetworkAdapter_Id(adapter);
        }
    }
    kFinally
    {
        kDestroyRef(&info);

        kEndFinally();
    }

    return kOK;
}

//[Deprecated]
kFx(kStatus) kNetwork_LocalIpInterfacesEx(kArrayList interfaces, kBool includeLoopback)
{
    kNetworkInfo info = kNULL; 

    kTry
    {
        kTest(kNetworkInfo_Construct(&info, kNULL)); 

        kTest(kArrayList_Allocate(interfaces, kTypeOf(kIpEntry), 0));

        for (kSize i = 0; i < kNetworkInfo_InterfaceCount(info); ++i)
        {
            kNetworkInterface interfaceAt = kNetworkInfo_InterfaceAt(info, i);
            kNetworkAdapter adapter = kNetworkInterface_Adapter(interfaceAt);
            kIpAddress address = kNetworkInterface_Address(interfaceAt);
            const kChar* name = kNetworkAdapter_Name(adapter);
 
            if (includeLoopback || !kIpAddress_IsLoopback(address))
            {
                kTest(kArrayList_AddCount(interfaces, 1)); 
                
                kIpEntry* entry = kArrayList_LastT(interfaces, kIpEntry);

                entry->address = address; 
                kTest(kStrCopy(entry->name, sizeof(entry->name), name));
            }
        }        
    }
    kFinally
    {
        kDestroyRef(&info);

        kEndFinally();
    }

    return kOK;
}

kFx(kStatus) kNetwork_FindAdapterNameById(kSize id, kChar* name, kSize capacity)
{
    kNetworkInfo info = kNULL; 
    kNetworkAdapter adapter = kNULL;

    kTry
    {
        kTest(kNetworkInfo_Construct(&info, kNULL)); 
        
        kTest(kNetworkInfo_FindAdapterById(info, id, &adapter)); 

        if (!kIsNull(name))
        {
            kTest(kStrCopy(name, capacity, kNetworkAdapter_Name(adapter))); 
        }
    }
    kFinally
    {
        kDestroyRef(&info);

        kEndFinally();
    }

    return kOK;
}

kFx(kStatus) kNetwork_FindAdapterNameByInterface(kIpAddress address, kChar* adapterName, kSize capacity)
{
    kNetworkInfo info = kNULL; 
    kNetworkInterface iface = kNULL;

    kTry
    {
        kTest(kNetworkInfo_Construct(&info, kNULL)); 
        
        kTest(kNetworkInfo_FindInterfaceByAddress(info, address, &iface)); 

        if (!kIsNull(adapterName))
        {
            kTest(kStrCopy(adapterName, capacity, kNetworkAdapter_Name(kNetworkInterface_Adapter(iface)))); 
        }
    }
    kFinally
    {
        kDestroyRef(&info);

        kEndFinally();
    }

    return kOK;
}

kFx(kStatus) kNetwork_FindFirstAdapterInterface(const kChar* adapterName, kIpAddress* address)
{
    kNetworkInfo info = kNULL; 
    kNetworkAdapter adapter = kNULL;

    kTry
    {
        kTest(kNetworkInfo_Construct(&info, kNULL)); 
        
        kTest(kNetworkInfo_FindAdapterByName(info, adapterName, &adapter)); 

        kTestTrue(kNetworkAdapter_InterfaceCount(adapter) > 0, kERROR_NOT_FOUND);

        if (!kIsNull(address))
        {
            *address = kNetworkInterface_Address(kNetworkAdapter_InterfaceAt(adapter, 0));
        }
    }
    kFinally
    {
        kDestroyRef(&info);

        kEndFinally();
    }

    return kOK;
}

kFx(kStatus) kNetwork_FindInterfaceByNetwork(kIpAddress network, kIpAddress* interfaceAddress)
{
    kNetworkInfo info = kNULL; 
    kNetworkInterface iface = kNULL;

    kTry
    {
        kTest(kNetworkInfo_Construct(&info, kNULL)); 
        
        kTest(kNetworkInfo_FindInterfaceByNetwork(info, network, &iface));
        
        if (!kIsNull(interfaceAddress))
        {
            *interfaceAddress = kNetworkInterface_Address(iface);
        }
    }
    kFinally
    {
        kDestroyRef(&info);

        kEndFinally();
    }

    return kOK;
}

#if defined(K_WINDOWS)

kFx(kStatus) xkNetwork_InitStaticPlatform()
{
    kStaticObj(kNetwork);

    sobj->notificationHandle = kNULL; 
    sobj->changeSem = kNULL;

    if (kApiLib_NetworkInitializationEnabled())
    {
        WORD wVersionRequested = MAKEWORD(2, 2);  
        WSADATA wsaData;

        kCheck(WSAStartup(wVersionRequested, &wsaData) == 0); 

        if((LOBYTE(wsaData.wVersion) != 2) || (HIBYTE(wsaData.wVersion) != 2)) 
        {
            WSACleanup();
            return kERROR_NETWORK;
        }
    }
 
    return kOK; 
}

kFx(kStatus) xkNetwork_ReleaseStaticPlatform()
{    
    if (kApiLib_NetworkInitializationEnabled())
    {
        WSACleanup(); 
    }

    return kOK; 
}

kFx(kStatus) xkNetwork_DefaultQueryNetInfo(kNetworkInfo info)
{
    ULONG bufferLength = 0;
    k32u opStatus;
    PIP_ADAPTER_ADDRESSES result = NULL;
    const k32u MAX_ITERATIONS = 16;  //arbitrary; 2 should be fine
    k32u iterationCount = 0;

    kTry
    {
        //get initial guess for required buffer size
        GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, NULL, kNULL, &bufferLength);

        //the required buffer size may change between when we request it and when it's needed (below); iterate 
        do
        {
            kTest(kMemAlloc(bufferLength, &result));

            opStatus = GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, NULL, result, &bufferLength);

            if (opStatus != ERROR_SUCCESS)
            {
                kMemFreeRef(&result); 

                if (opStatus != ERROR_BUFFER_OVERFLOW)
                {
                    kThrow(kERROR_NETWORK); 
                }
            }
     
            iterationCount++; 
        } 
        while ((opStatus != ERROR_SUCCESS) && (iterationCount < MAX_ITERATIONS));

        kTestTrue(opStatus == ERROR_SUCCESS, kERROR_NETWORK);

        //parse the results
        PIP_ADAPTER_ADDRESSES it = result;

        while (!kIsNull(it))
        {
            PIP_ADAPTER_UNICAST_ADDRESS unicastIt = it->FirstUnicastAddress;
            kNetworkAdapter adapter = kNULL;
            kText128 name;
            kBool isUp = (it->OperStatus == IfOperStatusUp);

            if (WideCharToMultiByte(CP_UTF8, 0, it->FriendlyName, -1, name, kCountOf(name), kNULL, kNULL) == 0)
            {
                name[0] = '\0';
            }

            kTest(xkNetworkInfo_AddAdapter(info, name, it->IfIndex, isUp, &adapter));
        
            while (!kIsNull(unicastIt))
            {
                struct sockaddr_in* addr = (struct sockaddr_in *) unicastIt->Address.lpSockaddr;
                k32u prefixLength = 32;
                kIpAddress address;
                    
#               if (_WIN32_WINNT >= _WIN32_WINNT_VISTA) 
                {
                    prefixLength = unicastIt->OnLinkPrefixLength;
                }
#               endif

                if (kSuccess(xkIpAddress_FromSockAddr(addr, &address, kNULL)))
                {
                    kTest(xkNetworkAdapter_AddInterface(adapter, address, prefixLength, kNULL));                     
                }
        
                unicastIt = unicastIt->Next;
            }
        
            it = it->Next;
        }      
    }
    kFinally
    {
        kMemFreeRef(&result);

        kEndFinally();
    }

    return kOK;
}

kFx(kBool) xkNetwork_DefaultQueryNetChange(k64u timeout)
{
    kStaticObj(kNetwork);
    
    return kSuccess(kSemaphore_Wait(sobj->changeSem, timeout));
}

#if (_WIN32_WINNT >= _WIN32_WINNT_VISTA) 
              
kFx(void) xkNetwork_OnChangeNotification(kPointer context, PMIB_IPINTERFACE_ROW row, MIB_NOTIFICATION_TYPE notificationType)
{
    kStaticObj(kNetwork);
    
    kSemaphore_Post(sobj->changeSem);
}

kFx(kStatus) xkNetwork_OsBeginChangeNotification()
{
    kStaticObj(kNetwork);

    kCheck(kSemaphore_Construct(&sobj->changeSem, 0, kNULL));

    kCheckTrue(NotifyIpInterfaceChange(AF_INET, xkNetwork_OnChangeNotification, kNULL, kFALSE, &sobj->notificationHandle) == NO_ERROR, kERROR_OS); 

    return kOK;
}

kFx(kStatus) xkNetwork_OsEndChangeNotification()
{
    kStaticObj(kNetwork);

    if (!kIsNull(sobj->notificationHandle))
    {
        CancelMibChangeNotify2(sobj->notificationHandle);
        sobj->notificationHandle = kNULL;
    }

    kDestroyRef(&sobj->changeSem);

    return kOK;
}

#else  

kFx(kStatus) xkNetwork_OsBeginChangeNotification()
{
    return kOK;
}

kFx(kStatus) xkNetwork_OsEndChangeNotification()
{
    return kOK;
}

#endif  //#if (_WIN32_WINNT >= _WIN32_WINNT_VISTA) 

#elif defined (K_LINUX) 

kFx(kStatus) xkNetwork_InitStaticPlatform()
{
    kStaticObj(kNetwork);

    sobj->netlinkSocket = kNULL;

    return kOK;
}

kFx(kStatus) xkNetwork_ReleaseStaticPlatform()
{
    return kOK;
}

kFx(kStatus) xkNetwork_DefaultQueryNetInfo(kNetworkInfo info)
{
    struct ifaddrs *ifaList = kNULL; 
    kNetworkAdapter adapter = kNULL;

    kTry
    {
        kTestTrue(getifaddrs(&ifaList) == 0, kERROR_NETWORK);
               
        struct ifaddrs *ifaListIt = ifaList; 
        
        while (!kIsNull(ifaListIt))
        {
            if ((ifaListIt->ifa_addr != kNULL) && (ifaListIt->ifa_addr->sa_family == AF_INET))
            {
                const kChar* name = ifaListIt->ifa_name;
                struct sockaddr_in* sockAddr = (struct sockaddr_in*) ifaListIt->ifa_addr;
                kIpAddress address = kIpAddress_FromNet32u(sockAddr->sin_addr.s_addr); 
                k32u prefixLength = 0; 

                if (!kIsNull(ifaListIt->ifa_netmask))
                {
                    struct sockaddr_in* sockMask = (struct sockaddr_in*) ifaListIt->ifa_netmask;
                    kIpAddress mask = kIpAddress_FromNet32u(sockMask->sin_addr.s_addr); 
                    prefixLength = xkIpAddress_SubnetMaskToPrefix(kIpAddress_ToHost32u(mask));
                }
                
                if (!kSuccess(kNetworkInfo_FindAdapterByName(info, name, &adapter)))
                {
                    kSize id = (kSize) if_nametoindex(name);
                    kBool isUp = ((ifaListIt->ifa_flags & IFF_UP) != 0);

                    kTest(xkNetworkInfo_AddAdapter(info, name, id, isUp, &adapter));
                }

                kTest(xkNetworkAdapter_AddInterface(adapter, address, prefixLength, kNULL));
            }

            ifaListIt = ifaListIt->ifa_next;
        }
    }
    kFinally
    {
        freeifaddrs(ifaList);
        kEndFinally();
    }

    return kOK;
}

kFx(kStatus) xkNetwork_OsBeginChangeNotification()
{
    kStaticObj(kNetwork);
    int fd = (int) xkSOCKET_INVALID_SOCKET;
    struct sockaddr_nl netlink;

    kCheckTrue((fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) != (int)xkSOCKET_INVALID_SOCKET, kERROR_NETWORK);

    netlink.nl_family = AF_NETLINK; 
    netlink.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR;

    kCheckTrue(bind(fd, (struct sockaddr *)&netlink, sizeof(netlink)) == 0, kERROR_NETWORK);

    kCheck(xkSocket_ConstructFromHandle(&sobj->netlinkSocket, kIP_VERSION_4, xkSOCKET_TYPE_NETLINK, fd, kNULL));

    kCheck(kSocket_SetEvents(sobj->netlinkSocket, kSOCKET_EVENT_READ));

    return kOK;
}

kFx(kStatus) xkNetwork_OsEndChangeNotification()
{
    kStaticObj(kNetwork);

    kDestroyRef(&sobj->netlinkSocket);

    return kOK;
}

kFx(kBool) xkNetwork_DefaultQueryNetChange(k64u timeout)
{
    kStaticObj(kNetwork);
    kSize bytesRead; 

    if (kSuccess(kSocket_Wait(sobj->netlinkSocket, timeout)))
    {        
        if (kSuccess(kSocket_ReadFrom(sobj->netlinkSocket, kNULL, sobj->buffer, sizeof(sobj->buffer), &bytesRead)))
        {
            return kTRUE;
        }
    }

    return kFALSE;
}

#elif defined (K_VX_KERNEL)

kFx(kStatus) xkNetwork_InitStaticPlatform()
{
    kStaticObj(kNetwork);

    sobj->netlinkSocket = kNULL;

    return kOK;
}

kFx(kStatus) xkNetwork_ReleaseStaticPlatform()
{
    return kOK;
}

//note: same as Linux
kFx(kStatus) xkNetwork_DefaultQueryNetInfo(kNetworkInfo info)
{
    struct ifaddrs *ifaList = kNULL; 
    kNetworkAdapter adapter = kNULL;

    kTry
    {
        kTestTrue(getifaddrs(&ifaList) == 0, kERROR_NETWORK);
               
        struct ifaddrs *ifaListIt = ifaList; 
        
        while (!kIsNull(ifaListIt))
        {
            if ((ifaListIt->ifa_addr != kNULL) && (ifaListIt->ifa_addr->sa_family == AF_INET))
            {
                const kChar* name = ifaListIt->ifa_name;
                struct sockaddr_in* sockAddr = (struct sockaddr_in*) ifaListIt->ifa_addr;
                kIpAddress address = kIpAddress_FromNet32u(sockAddr->sin_addr.s_addr); 
                k32u prefixLength = 0; 

                if (!kIsNull(ifaListIt->ifa_netmask))
                {
                    struct sockaddr_in* sockMask = (struct sockaddr_in*) ifaListIt->ifa_netmask;
                    kIpAddress mask = kIpAddress_FromNet32u(sockMask->sin_addr.s_addr); 
                    prefixLength = xkIpAddress_SubnetMaskToPrefix(kIpAddress_ToHost32u(mask));
                }
                
                if (!kSuccess(kNetworkInfo_FindAdapterByName(info, name, &adapter)))
                {
                    kSize id = (kSize) if_nametoindex(name);
                    kBool isUp = ((ifaListIt->ifa_flags & IFF_UP) != 0);

                    kTest(xkNetworkInfo_AddAdapter(info, name, id, isUp, &adapter));
                }

                kTest(xkNetworkAdapter_AddInterface(adapter, address, prefixLength, kNULL));
            }

            ifaListIt = ifaListIt->ifa_next;
        }
    }
    kFinally
    {
        freeifaddrs(ifaList);
        kEndFinally();
    }

    return kOK;
}

kFx(kStatus) xkNetwork_OsBeginChangeNotification()
{
    kStaticObj(kNetwork);
    int fd = (int) xkSOCKET_INVALID_SOCKET;
    struct Ip_sockaddr_nl netlink;

    kCheckTrue((fd = socket(PF_NETLINK, SOCK_RAW, IP_NETLINK_ROUTE)) != (int)xkSOCKET_INVALID_SOCKET, kERROR_NETWORK);

    netlink.nl_family = AF_NETLINK; 
    netlink.nl_len = sizeof(netlink);
    netlink.nl_groups = IP_RTMGRP_IPV4_IFADDR | IP_RTMGRP_LINK;

    kCheckTrue(bind(fd, (struct sockaddr *)&netlink, sizeof(netlink)) == 0, kERROR_NETWORK);

    kCheck(xkSocket_ConstructFromHandle(&sobj->netlinkSocket, kIP_VERSION_4, xkSOCKET_TYPE_NETLINK, fd, kNULL));

    kCheck(kSocket_SetEvents(sobj->netlinkSocket, kSOCKET_EVENT_READ));

    return kOK;
}

kFx(kStatus) xkNetwork_OsEndChangeNotification()
{
    kStaticObj(kNetwork);

    kDestroyRef(&sobj->netlinkSocket);

    return kOK;
}

kFx(kBool) xkNetwork_DefaultQueryNetChange(k64u timeout)
{
    kStaticObj(kNetwork);
    kSize bytesRead; 

    if (kSuccess(kSocket_Wait(sobj->netlinkSocket, timeout)))
    {
        if (kSuccess(kSocket_ReadFrom(sobj->netlinkSocket, kNULL, sobj->buffer, sizeof(sobj->buffer), &bytesRead)))
        {
            return kTRUE;
        }
    }

    return kFALSE;
}

#elif defined(K_QNX)

kFx(kStatus) xkNetwork_InitStaticPlatform()
{
    kStaticObj(kNetwork);

    //unused
    sobj->netlinkSocket = kNULL;

    return kOK;
}

kFx(kStatus) xkNetwork_ReleaseStaticPlatform()
{
    return kOK;
}

//note: same as Linux
kFx(kStatus) xkNetwork_DefaultQueryNetInfo(kNetworkInfo info)
{
    struct ifaddrs *ifaList = kNULL; 
    kNetworkAdapter adapter = kNULL;

    kTry
    {
        kTestTrue(getifaddrs(&ifaList) == 0, kERROR_NETWORK);
               
        struct ifaddrs *ifaListIt = ifaList; 
        
        while (!kIsNull(ifaListIt))
        {
            if ((ifaListIt->ifa_addr != kNULL) && (ifaListIt->ifa_addr->sa_family == AF_INET))
            {
                const kChar* name = ifaListIt->ifa_name;
                struct sockaddr_in* sockAddr = (struct sockaddr_in*) ifaListIt->ifa_addr;
                kIpAddress address = kIpAddress_FromNet32u(sockAddr->sin_addr.s_addr); 
                k32u prefixLength = 0; 

                if (!kIsNull(ifaListIt->ifa_netmask))
                {
                    struct sockaddr_in* sockMask = (struct sockaddr_in*) ifaListIt->ifa_netmask;
                    kIpAddress mask = kIpAddress_FromNet32u(sockMask->sin_addr.s_addr); 
                    prefixLength = xkIpAddress_SubnetMaskToPrefix(kIpAddress_ToHost32u(mask));
                }
                
                if (!kSuccess(kNetworkInfo_FindAdapterByName(info, name, &adapter)))
                {
                    kSize id = (kSize) if_nametoindex(name);
                    kBool isUp = ((ifaListIt->ifa_flags & IFF_UP) != 0);

                    kTest(xkNetworkInfo_AddAdapter(info, name, id, isUp, &adapter));
                }

                kTest(xkNetworkAdapter_AddInterface(adapter, address, prefixLength, kNULL));
            }

            ifaListIt = ifaListIt->ifa_next;
        }
    }
    kFinally
    {
        freeifaddrs(ifaList);
        kEndFinally();
    }

    return kOK;
}

kFx(kStatus) xkNetwork_OsBeginChangeNotification()
{
    return kOK;
}

kFx(kStatus) xkNetwork_OsEndChangeNotification()
{
    return kOK;
}

kFx(kBool) xkNetwork_DefaultQueryNetChange(k64u timeout)
{
    //prevent spinning
    kThread_Sleep(timeout);

    return kFALSE;
}

#else

kFx(kStatus) xkNetwork_InitStaticPlatform()
{
    return kOK;
}

kFx(kStatus) xkNetwork_ReleaseStaticPlatform()
{
    return kOK;
}

kFx(kStatus) xkNetwork_DefaultQueryNetInfo(kNetworkInfo netInfo)
{
    return kERROR_UNIMPLEMENTED;
}

kFx(kStatus) xkNetwork_OsBeginChangeNotification()
{
    return kOK;
}

kFx(kStatus) xkNetwork_OsEndChangeNotification()
{
    return kOK;
}

kFx(kBool) xkNetwork_DefaultQueryNetChange(k64u timeout)
{
    //prevent spinning
    kThread_Sleep(timeout);

    return kFALSE;
}

#endif
