/*
 *  Copyright (c) 2016, The OpenThread Authors.
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *  3. Neither the name of the copyright holder nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 */

#include "precomp.h"
#include "otApi.tmh"

//#define DEBUG_ASYNC_IO

using namespace std;

// The maximum time we will wait for an overlapped result. Essentially, the maximum
// amount of time each synchronous IOCTL should take.
const DWORD c_MaxOverlappedWaitTimeMS = 5 * 1000;

// Version string returned by the API
const char c_Version[] = "Windows"; // TODO - What should we really put here?

template<class CallbackT>
class otCallback
{
public:
    RTL_REFERENCE_COUNT CallbackRefCount;
    HANDLE              CallbackCompleteEvent;
    GUID                InterfaceGuid;
    CallbackT           Callback;
    PVOID               CallbackContext;

    otCallback(
        CallbackT _Callback,
        PVOID _CallbackContext
        ) : 
        CallbackRefCount(1),
        CallbackCompleteEvent(CreateEvent(nullptr, FALSE, FALSE, nullptr)),
        Callback(_Callback),
        CallbackContext(_CallbackContext)
    {
    }

    otCallback(
        const GUID& _InterfaceGuid,
        CallbackT _Callback,
        PVOID _CallbackContext
        ) : 
        CallbackRefCount(1),
        CallbackCompleteEvent(CreateEvent(nullptr, FALSE, FALSE, nullptr)),
        InterfaceGuid(_InterfaceGuid),
        Callback(_Callback),
        CallbackContext(_CallbackContext)
    {
    }

    ~otCallback()
    {
        if (CallbackCompleteEvent) CloseHandle(CallbackCompleteEvent);
    }

    void AddRef()
    {
        RtlIncrementReferenceCount(&CallbackRefCount);
    }

    void Release(bool waitForShutdown = false)
    {
        if (RtlDecrementReferenceCount(&CallbackRefCount))
        {
            // Set completion event if there are no more refs
            SetEvent(CallbackCompleteEvent);
        }

        if (waitForShutdown)
        {
            WaitForSingleObject(CallbackCompleteEvent, INFINITE);
        }
    }
};

typedef otCallback<otDeviceAvailabilityChangedCallback> otApiDeviceAvailabilityCallback;
typedef otCallback<otHandleActiveScanResult> otApiActiveScanCallback;
typedef otCallback<otHandleEnergyScanResult> otApiEnergyScanCallback;
typedef otCallback<otStateChangedCallback> otApiStateChangeCallback;
typedef otCallback<otCommissionerEnergyReportCallback> otApiCommissionerEnergyReportCallback;
typedef otCallback<otCommissionerPanIdConflictCallback> otApiCommissionerPanIdConflictCallback;

typedef struct otApiInstance
{
    // Handle to the driver
    HANDLE                      DeviceHandle;

    // Async IO variables
    OVERLAPPED                  Overlapped;
    PTP_WAIT                    ThreadpoolWait;

    // Notification variables
    CRITICAL_SECTION            CallbackLock;
    OTLWF_NOTIFICATION          NotificationBuffer;

    // Callbacks
    otApiDeviceAvailabilityCallback*    DeviceAvailabilityCallbacks;
    vector<otApiActiveScanCallback*>    ActiveScanCallbacks;
    vector<otApiEnergyScanCallback*>    EnergyScanCallbacks;
    vector<otApiActiveScanCallback*>    DiscoverCallbacks;
    vector<otApiStateChangeCallback*>   StateChangedCallbacks;
    vector<otApiCommissionerEnergyReportCallback*>  CommissionerEnergyReportCallbacks;
    vector<otApiCommissionerPanIdConflictCallback*> CommissionerPanIdConflictCallbacks;

    // Constructor
    otApiInstance() : 
        DeviceHandle(INVALID_HANDLE_VALUE),
        Overlapped({0}),
        ThreadpoolWait(nullptr),
        DeviceAvailabilityCallbacks(nullptr)
    { 
        InitializeCriticalSection(&CallbackLock);
    }

    ~otApiInstance()
    {
        DeleteCriticalSection(&CallbackLock);
    }

    // Helper function to set a callback
    template<class CallbackT>
    bool 
    SetCallback(
        vector<otCallback<CallbackT>*> &Callbacks, 
        const GUID& InterfaceGuid,
        CallbackT Callback,
        PVOID CallbackContext
        )
    {
        bool alreadyExists = false;
        otCallback<CallbackT>* CallbackToRelease = nullptr;

        EnterCriticalSection(&CallbackLock);

        if (Callback == nullptr)
        {
            for (size_t i = 0; i < Callbacks.size(); i++)
            {
                if (Callbacks[i]->InterfaceGuid == InterfaceGuid)
                {
                    CallbackToRelease = Callbacks[i];
                    Callbacks.erase(Callbacks.begin() + i);
                    break;
                }
            }
        }
        else
        {
            for (size_t i = 0; i < Callbacks.size(); i++)
            {
                if (Callbacks[i]->InterfaceGuid == InterfaceGuid)
                {
                    alreadyExists = true;
                    break;
                }
            }

            if (!alreadyExists)
            {
                Callbacks.push_back(new otCallback<CallbackT>(InterfaceGuid, Callback, CallbackContext));
            }
        }

        LeaveCriticalSection(&CallbackLock);

        if (CallbackToRelease)
        {
            CallbackToRelease->Release(true);
            delete CallbackToRelease;
        }

        return !alreadyExists;
    }

} otApiInstance;

typedef struct otInstance
{
    otApiInstance   *ApiHandle;      // Pointer to the Api handle
    NET_IFINDEX      InterfaceIndex; // Interface Index
    NET_LUID         InterfaceLuid;  // Interface Luid
    GUID             InterfaceGuid;  // Interface guid
    ULONG            CompartmentID;  // Interface Compartment ID

} otInstance;

// otpool wait callback for async IO completion
VOID CALLBACK 
otIoComplete(
    _Inout_     PTP_CALLBACK_INSTANCE Instance,
    _Inout_opt_ PVOID                 Context,
    _Inout_     PTP_WAIT              Wait,
    _In_        TP_WAIT_RESULT        WaitResult
    );

OTAPI 
otApiInstance *
OTCALL
otApiInit(
    )
{
    DWORD dwError = ERROR_SUCCESS;
    otApiInstance *aApitInstance = nullptr;
    
    otLogFuncEntry();

    aApitInstance = new(std::nothrow)otApiInstance();
    if (aApitInstance == nullptr)
    {
        dwError = GetLastError();
        otLogWarnApi("Failed to allocate otApiInstance");
        goto error;
    }

    // Open the pipe to the OpenThread driver
    aApitInstance->DeviceHandle = 
        CreateFile(
            OTLWF_IOCLT_PATH,
            GENERIC_READ | GENERIC_WRITE,
            0,
            nullptr,                // no SECURITY_ATTRIBUTES structure
            OPEN_EXISTING,          // No special create flags
            FILE_FLAG_OVERLAPPED,   // Allow asynchronous requests
            nullptr
            );
    if (aApitInstance->DeviceHandle == INVALID_HANDLE_VALUE)
    {
        dwError = GetLastError();
        otLogCritApi("CreateFile failed, %!WINERROR!", dwError);
        goto error;
    }

    // Create event for completion of async IO
    aApitInstance->Overlapped.hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
    if (aApitInstance->Overlapped.hEvent == nullptr)
    {
        dwError = GetLastError();
        otLogCritApi("CreateEvent (Overlapped.hEvent) failed, %!WINERROR!", dwError);
        goto error;
    }

    // Create the otpool wait
    aApitInstance->ThreadpoolWait = 
        CreateThreadpoolWait(
            otIoComplete,
            aApitInstance,
            nullptr
            );
    if (aApitInstance->ThreadpoolWait == nullptr)
    {
        dwError = GetLastError();
        otLogCritApi("CreateThreadpoolWait failed, %!WINERROR!", dwError);
        goto error;
    }

    // Start the otpool waiting on the overlapped event
    SetThreadpoolWait(aApitInstance->ThreadpoolWait, aApitInstance->Overlapped.hEvent, nullptr);

#ifdef DEBUG_ASYNC_IO
    otLogDebgApi("Querying for 1st notification");
#endif

    // Request first notification asynchronously
    if (!DeviceIoControl(
            aApitInstance->DeviceHandle,
            IOCTL_OTLWF_QUERY_NOTIFICATION,
            nullptr, 0,
            &aApitInstance->NotificationBuffer, sizeof(OTLWF_NOTIFICATION),
            nullptr, 
            &aApitInstance->Overlapped))
    {
        dwError = GetLastError();
        if (dwError != ERROR_IO_PENDING)
        {
            otLogCritApi("DeviceIoControl for first notification failed, %!WINERROR!", dwError);
            goto error;
        }
        dwError = ERROR_SUCCESS;
    }

error:

    if (dwError != ERROR_SUCCESS)
    {
        otApiFinalize(aApitInstance);
        aApitInstance = nullptr;
    }
    
    otLogFuncExit();

    return aApitInstance;
}

OTAPI 
void 
OTCALL
otApiFinalize(
    _In_ otApiInstance *aApitInstance
)
{
    if (aApitInstance == nullptr) return;
    
    otLogFuncEntry();

    // If we never got the handle, nothing left to clean up
    if (aApitInstance->DeviceHandle != INVALID_HANDLE_VALUE)
    {
        //
        // Make sure we unregister callbacks
        //

        EnterCriticalSection(&aApitInstance->CallbackLock);

        otApiDeviceAvailabilityCallback* DeviceAvailabilityCallbacks = aApitInstance->DeviceAvailabilityCallbacks;
        aApitInstance->DeviceAvailabilityCallbacks = nullptr;

        vector<otApiActiveScanCallback*> ActiveScanCallbacks(aApitInstance->ActiveScanCallbacks);
        aApitInstance->ActiveScanCallbacks.clear();

        vector<otApiEnergyScanCallback*> EnergyScanCallbacks(aApitInstance->EnergyScanCallbacks);
        aApitInstance->EnergyScanCallbacks.clear();

        vector<otApiActiveScanCallback*> DiscoverCallbacks(aApitInstance->DiscoverCallbacks);
        aApitInstance->DiscoverCallbacks.clear();

        vector<otApiStateChangeCallback*> StateChangedCallbacks(aApitInstance->StateChangedCallbacks);
        aApitInstance->StateChangedCallbacks.clear();

        vector<otApiCommissionerEnergyReportCallback*> CommissionerEnergyReportCallbacks(aApitInstance->CommissionerEnergyReportCallbacks);
        aApitInstance->CommissionerEnergyReportCallbacks.clear();

        vector<otApiCommissionerPanIdConflictCallback*> CommissionerPanIdConflictCallbacks(aApitInstance->CommissionerPanIdConflictCallbacks);
        aApitInstance->CommissionerPanIdConflictCallbacks.clear();

        #ifdef DEBUG_ASYNC_IO
        otLogDebgApi("Clearing Threadpool Wait");
        #endif

        // Clear the threadpool wait to prevent further waits from being scheduled
        PTP_WAIT tpWait = aApitInstance->ThreadpoolWait;
        aApitInstance->ThreadpoolWait = nullptr;

        LeaveCriticalSection(&aApitInstance->CallbackLock);

        // Clear all callbacks
        if (DeviceAvailabilityCallbacks)
        {
            DeviceAvailabilityCallbacks->Release(true);
            delete DeviceAvailabilityCallbacks;
        }
        for (size_t i = 0; i < ActiveScanCallbacks.size(); i++)
        {
            ActiveScanCallbacks[i]->Release(true);
            delete ActiveScanCallbacks[i];
        }
        for (size_t i = 0; i < EnergyScanCallbacks.size(); i++)
        {
            EnergyScanCallbacks[i]->Release(true);
            delete EnergyScanCallbacks[i];
        }
        for (size_t i = 0; i < DiscoverCallbacks.size(); i++)
        {
            DiscoverCallbacks[i]->Release(true);
            delete DiscoverCallbacks[i];
        }
        for (size_t i = 0; i < StateChangedCallbacks.size(); i++)
        {
            StateChangedCallbacks[i]->Release(true);
            delete StateChangedCallbacks[i];
        }
        for (size_t i = 0; i < CommissionerEnergyReportCallbacks.size(); i++)
        {
            CommissionerEnergyReportCallbacks[i]->Release(true);
            delete CommissionerEnergyReportCallbacks[i];
        }
        for (size_t i = 0; i < CommissionerPanIdConflictCallbacks.size(); i++)
        {
            CommissionerPanIdConflictCallbacks[i]->Release(true);
            delete CommissionerPanIdConflictCallbacks[i];
        }
        
        // Clean up threadpool wait
        if (tpWait)
        {
            #ifdef DEBUG_ASYNC_IO
            otLogDebgApi("Waiting for outstanding threadpool callbacks to compelte");
            #endif

            // Cancel any queued waits and wait for any outstanding calls to compelte
            WaitForThreadpoolWaitCallbacks(tpWait, TRUE);
        
            #ifdef DEBUG_ASYNC_IO
            otLogDebgApi("Cancelling any pending IO");
            #endif

            // Cancel any async IO
            CancelIoEx(aApitInstance->DeviceHandle, &aApitInstance->Overlapped);

            // Free the threadpool wait
            CloseThreadpoolWait(tpWait);
        }

        // Clean up overlapped event
        if (aApitInstance->Overlapped.hEvent)
        {
            CloseHandle(aApitInstance->Overlapped.hEvent);
        }
    
        // Close the device handle
        CloseHandle(aApitInstance->DeviceHandle);
    }

    delete aApitInstance;
    
    otLogFuncExit();
}

OTAPI 
void 
OTCALL
otFreeMemory(
    _In_ const void *mem
    )
{
    free((void*)mem);
}

// Handles cleanly invoking the register callback
VOID
ProcessNotification(
    _In_ otApiInstance         *aApitInstance,
    _In_ POTLWF_NOTIFICATION    Notif
    )
{
    if (Notif->NotifType == OTLWF_NOTIF_DEVICE_AVAILABILITY)
    {
        otCallback<otDeviceAvailabilityChangedCallback>* Callback = nullptr;
        
        EnterCriticalSection(&aApitInstance->CallbackLock);

        if (aApitInstance->DeviceAvailabilityCallbacks != nullptr)
        {
            aApitInstance->DeviceAvailabilityCallbacks->AddRef();
            Callback = aApitInstance->DeviceAvailabilityCallbacks;
        }

        LeaveCriticalSection(&aApitInstance->CallbackLock);

        // Invoke the callback outside the lock and release ref when done
        if (Callback)
        {
            Callback->Callback(
                Notif->DeviceAvailabilityPayload.Available != FALSE, 
                &Notif->InterfaceGuid, 
                Callback->CallbackContext);

            Callback->Release();
        }
    }
    else if (Notif->NotifType == OTLWF_NOTIF_STATE_CHANGE)
    {
        otCallback<otStateChangedCallback>* Callback = nullptr;

        EnterCriticalSection(&aApitInstance->CallbackLock);

        for (size_t i = 0; i < aApitInstance->StateChangedCallbacks.size(); i++)
        {
            if (aApitInstance->StateChangedCallbacks[i]->InterfaceGuid == Notif->InterfaceGuid)
            {
                aApitInstance->StateChangedCallbacks[i]->AddRef();
                Callback = aApitInstance->StateChangedCallbacks[i];
                break;
            }
        }

        LeaveCriticalSection(&aApitInstance->CallbackLock);
        
        // Invoke the callback outside the lock and release ref when done
        if (Callback)
        {
            Callback->Callback(
                Notif->StateChangePayload.Flags, 
                Callback->CallbackContext);

            Callback->Release();
        }
    }
    else if (Notif->NotifType == OTLWF_NOTIF_DISCOVER)
    {
        otCallback<otHandleActiveScanResult>* Callback = nullptr;

        EnterCriticalSection(&aApitInstance->CallbackLock);

        for (size_t i = 0; i < aApitInstance->DiscoverCallbacks.size(); i++)
        {
            if (aApitInstance->DiscoverCallbacks[i]->InterfaceGuid == Notif->InterfaceGuid)
            {
                aApitInstance->DiscoverCallbacks[i]->AddRef();
                Callback = aApitInstance->DiscoverCallbacks[i];
                break;
            }
        }

        LeaveCriticalSection(&aApitInstance->CallbackLock);
        
        // Invoke the callback outside the lock and release ref when done
        if (Callback)
        {
            Callback->Callback(
                Notif->DiscoverPayload.Valid ? &Notif->DiscoverPayload.Results : nullptr, 
                Callback->CallbackContext);

            Callback->Release();
        }
    }
    else if (Notif->NotifType == OTLWF_NOTIF_ACTIVE_SCAN)
    {
        otCallback<otHandleActiveScanResult>* Callback = nullptr;

        EnterCriticalSection(&aApitInstance->CallbackLock);

        for (size_t i = 0; i < aApitInstance->ActiveScanCallbacks.size(); i++)
        {
            if (aApitInstance->ActiveScanCallbacks[i]->InterfaceGuid == Notif->InterfaceGuid)
            {
                aApitInstance->ActiveScanCallbacks[i]->AddRef();
                Callback = aApitInstance->ActiveScanCallbacks[i];
                break;
            }
        }

        LeaveCriticalSection(&aApitInstance->CallbackLock);
        
        // Invoke the callback outside the lock and release ref when done
        if (Callback)
        {
            Callback->Callback(
                Notif->ActiveScanPayload.Valid ? &Notif->ActiveScanPayload.Results : nullptr, 
                Callback->CallbackContext);

            Callback->Release();
        }
    }
    else if (Notif->NotifType == OTLWF_NOTIF_ENERGY_SCAN)
    {
        otCallback<otHandleEnergyScanResult>* Callback = nullptr;

        EnterCriticalSection(&aApitInstance->CallbackLock);

        for (size_t i = 0; i < aApitInstance->EnergyScanCallbacks.size(); i++)
        {
            if (aApitInstance->EnergyScanCallbacks[i]->InterfaceGuid == Notif->InterfaceGuid)
            {
                aApitInstance->EnergyScanCallbacks[i]->AddRef();
                Callback = aApitInstance->EnergyScanCallbacks[i];
                break;
            }
        }

        LeaveCriticalSection(&aApitInstance->CallbackLock);
        
        // Invoke the callback outside the lock and release ref when done
        if (Callback)
        {
            Callback->Callback(
                Notif->EnergyScanPayload.Valid ? &Notif->EnergyScanPayload.Results : nullptr, 
                Callback->CallbackContext);

            Callback->Release();
        }
    }
    else if (Notif->NotifType == OTLWF_NOTIF_COMMISSIONER_ENERGY_REPORT)
    {
        otCallback<otCommissionerEnergyReportCallback>* Callback = nullptr;

        EnterCriticalSection(&aApitInstance->CallbackLock);

        for (size_t i = 0; i < aApitInstance->CommissionerEnergyReportCallbacks.size(); i++)
        {
            if (aApitInstance->CommissionerEnergyReportCallbacks[i]->InterfaceGuid == Notif->InterfaceGuid)
            {
                aApitInstance->CommissionerEnergyReportCallbacks[i]->AddRef();
                Callback = aApitInstance->CommissionerEnergyReportCallbacks[i];
                break;
            }
        }

        LeaveCriticalSection(&aApitInstance->CallbackLock);
        
        // Invoke the callback outside the lock and release ref when done
        if (Callback)
        {
            Callback->Callback(
                Notif->CommissionerEnergyReportPayload.ChannelMask,
                Notif->CommissionerEnergyReportPayload.EnergyList,
                Notif->CommissionerEnergyReportPayload.EnergyListLength,
                Callback->CallbackContext);

            Callback->Release();
        }
    }
    else if (Notif->NotifType == OTLWF_NOTIF_COMMISSIONER_PANID_QUERY)
    {
        otCallback<otCommissionerPanIdConflictCallback>* Callback = nullptr;

        EnterCriticalSection(&aApitInstance->CallbackLock);

        for (size_t i = 0; i < aApitInstance->CommissionerPanIdConflictCallbacks.size(); i++)
        {
            if (aApitInstance->CommissionerPanIdConflictCallbacks[i]->InterfaceGuid == Notif->InterfaceGuid)
            {
                aApitInstance->CommissionerPanIdConflictCallbacks[i]->AddRef();
                Callback = aApitInstance->CommissionerPanIdConflictCallbacks[i];
                break;
            }
        }

        LeaveCriticalSection(&aApitInstance->CallbackLock);
        
        // Invoke the callback outside the lock and release ref when done
        if (Callback)
        {
            Callback->Callback(
                Notif->CommissionerPanIdQueryPayload.PanId,
                Notif->CommissionerPanIdQueryPayload.ChannelMask,
                Callback->CallbackContext);

            Callback->Release();
        }
    }
    else
    {
        // Unexpected notif type
    }
}

// Threadpool wait callback for async IO completion
VOID CALLBACK 
otIoComplete(
    _Inout_     PTP_CALLBACK_INSTANCE /* Instance */,
    _Inout_opt_ PVOID                 Context,
    _Inout_     PTP_WAIT              /* Wait */,
    _In_        TP_WAIT_RESULT        /* WaitResult */
    )
{
#ifdef DEBUG_ASYNC_IO
    otLogFuncEntry();
#endif

    otApiInstance *aApitInstance = (otApiInstance*)Context;
    if (aApitInstance == nullptr) return;

    // Get the result of the IO operation
    DWORD dwBytesTransferred = 0;
    if (!GetOverlappedResult(
            aApitInstance->DeviceHandle,
            &aApitInstance->Overlapped,
            &dwBytesTransferred,
            FALSE))
    {
        DWORD dwError = GetLastError();
        otLogCritApi("GetOverlappedResult for notification failed, %!WINERROR!", dwError);
    }
    else
    {
        otLogDebgApi("Received successful callback for notification, type=%d", 
                     aApitInstance->NotificationBuffer.NotifType);

        // Invoke the callback if set
        ProcessNotification(aApitInstance, &aApitInstance->NotificationBuffer);
            
        // Try to get the threadpool wait to see if we are allowed to continue processing notifications
        EnterCriticalSection(&aApitInstance->CallbackLock);
        PTP_WAIT tpWait = aApitInstance->ThreadpoolWait;
        LeaveCriticalSection(&aApitInstance->CallbackLock);

        if (tpWait)
        {
            // Start waiting for next notification
            SetThreadpoolWait(tpWait, aApitInstance->Overlapped.hEvent, nullptr);
            
#ifdef DEBUG_ASYNC_IO
            otLogDebgApi("Querying for next notification");
#endif

            // Request next notification
            if (!DeviceIoControl(
                    aApitInstance->DeviceHandle,
                    IOCTL_OTLWF_QUERY_NOTIFICATION,
                    nullptr, 0,
                    &aApitInstance->NotificationBuffer, sizeof(OTLWF_NOTIFICATION),
                    nullptr, 
                    &aApitInstance->Overlapped))
            {
                DWORD dwError = GetLastError();
                if (dwError != ERROR_IO_PENDING)
                {
                    otLogCritApi("DeviceIoControl for new notification failed, %!WINERROR!", dwError);
                }
            }
        }
    }
    
#ifdef DEBUG_ASYNC_IO
    otLogFuncExit();
#endif
}

DWORD
SendIOCTL(
    _In_ otApiInstance *aApitInstance,
    _In_ DWORD dwIoControlCode,
    _In_reads_bytes_opt_(nInBufferSize) LPVOID lpInBuffer,
    _In_ DWORD nInBufferSize,
    _Out_writes_bytes_opt_(nOutBufferSize) LPVOID lpOutBuffer,
    _In_ DWORD nOutBufferSize
    )
{
    DWORD dwError = ERROR_SUCCESS;
    OVERLAPPED Overlapped = { 0 };
    DWORD dwBytesReturned = 0;
    
    Overlapped.hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
    if (Overlapped.hEvent == nullptr)
    {
        dwError = GetLastError();
        otLogCritApi("CreateEvent (Overlapped.hEvent) failed, %!WINERROR!", dwError);
        goto error;
    }
    
    // Send the IOCTL the OpenThread driver
    if (!DeviceIoControl(
            aApitInstance->DeviceHandle,
            dwIoControlCode,
            lpInBuffer, nInBufferSize,
            lpOutBuffer, nOutBufferSize,
            nullptr, 
            &Overlapped))
    {
        dwError = GetLastError();
        if (dwError != ERROR_IO_PENDING)
        {
            otLogCritApi("DeviceIoControl(0x%x) failed, %!WINERROR!", dwIoControlCode, dwError);
            goto error;
        }
        dwError = ERROR_SUCCESS;
    }

    // Get the result of the IO operation
    if (!GetOverlappedResultEx(
            aApitInstance->DeviceHandle,
            &Overlapped,
            &dwBytesReturned,
            c_MaxOverlappedWaitTimeMS,
            FALSE
            ))
    {
        dwError = GetLastError();
        if (dwError == WAIT_TIMEOUT)
        {
            dwError = ERROR_TIMEOUT;
            CancelIoEx(aApitInstance->DeviceHandle, &Overlapped);
        }
        otLogCritApi("GetOverlappedResult failed, %!WINERROR!", dwError);
        goto error;
    }

    if (dwBytesReturned != nOutBufferSize)
    {
        dwError = ERROR_INVALID_DATA;
        otLogCritApi("GetOverlappedResult returned invalid output size, expected=%u actual=%u", 
                     nOutBufferSize, dwBytesReturned);
        goto error;
    }

error:

    if (Overlapped.hEvent)
    {
        CloseHandle(Overlapped.hEvent);
    }

    return dwError;
}

__pragma(pack(push,1))
template <class T1, class T2>
struct PackedBuffer2
{
    T1 data1; T2 data2;
    PackedBuffer2(const T1 &d1, const T2 &d2) : data1(d1), data2(d2) { }
};
template <class T1, class T2, class T3>
struct PackedBuffer3
{
    T1 data1; T2 data2; T3 data3;
    PackedBuffer3(const T1 &d1, const T2 &d2, const T3 &d3) : data1(d1), data2(d2), data3(d3) { }
};
template <class T1, class T2, class T3, class T4>
struct PackedBuffer4
{
    T1 data1; T2 data2; T3 data3; T4 data4;
    PackedBuffer4(const T1 &d1, const T2 &d2, const T3 &d3, const T4 &d4) : data1(d1), data2(d2), data3(d3), data4(d4) { }
};
template <class T1, class T2, class T3, class T4, class T5>
struct PackedBuffer5
{
    T1 data1; T2 data2; T3 data3; T4 data4; T5 data5;
    PackedBuffer5(const T1 &d1, const T2 &d2, const T3 &d3, const T4 &d4, const T5 &d5) : data1(d1), data2(d2), data3(d3), data4(d4), data5(d5) { }
};
template <class T1, class T2, class T3, class T4, class T5, class T6>
struct PackedBuffer6
{
    T1 data1; T2 data2; T3 data3; T4 data4; T5 data5; T6 data6;
    PackedBuffer6(const T1 &d1, const T2 &d2, const T3 &d3, const T4 &d4, const T5 &d5, const T6 &d6) : data1(d1), data2(d2), data3(d3), data4(d4), data5(d5), data6(d6) { }
};
 __pragma(pack(pop))

template <class in, class out>
DWORD
QueryIOCTL(
    _In_ otInstance *aInstance,
    _In_ DWORD dwIoControlCode,
    _In_ const in *input,
    _Out_ out* output
    )
{
    PackedBuffer2<GUID,in> Buffer(aInstance->InterfaceGuid, *input);
    return SendIOCTL(aInstance->ApiHandle, dwIoControlCode, &Buffer, sizeof(Buffer), output, sizeof(out));
}

template <class out>
DWORD
QueryIOCTL(
    _In_ otInstance *aInstance,
    _In_ DWORD dwIoControlCode,
    _Out_ out* output
    )
{
    return SendIOCTL(aInstance->ApiHandle, dwIoControlCode, &aInstance->InterfaceGuid, sizeof(GUID), output, sizeof(out));
}

template <class in>
DWORD
SetIOCTL(
    _In_ otInstance *aInstance,
    _In_ DWORD dwIoControlCode,
    _In_ const in* input
    )
{
    PackedBuffer2<GUID,in> Buffer(aInstance->InterfaceGuid, *input);
    return SendIOCTL(aInstance->ApiHandle, dwIoControlCode, &Buffer, sizeof(Buffer), nullptr, 0);
}

template <class in>
DWORD
SetIOCTL(
    _In_ otInstance *aInstance,
    _In_ DWORD dwIoControlCode,
    _In_ const in input
    )
{
    PackedBuffer2<GUID,in> Buffer(aInstance->InterfaceGuid, input);
    return SendIOCTL(aInstance->ApiHandle, dwIoControlCode, &Buffer, sizeof(Buffer), nullptr, 0);
}

DWORD
SetIOCTL(
    _In_ otInstance *aInstance,
    _In_ DWORD dwIoControlCode
    )
{
    return SendIOCTL(aInstance->ApiHandle, dwIoControlCode, &aInstance->InterfaceGuid, sizeof(GUID), nullptr, 0);
}

ThreadError
DwordToThreadError(
    DWORD dwError
    )
{
    if (((int)dwError) > 0)
    {
        return kThreadError_Error;
    }
    else
    {
        return (ThreadError)(-(int)dwError);
    }
}

OTAPI 
void 
OTCALL
otSetDeviceAvailabilityChangedCallback(
    _In_ otApiInstance *aApitInstance,
    _In_ otDeviceAvailabilityChangedCallback aCallback,
    _In_ void *aCallbackContext
    )
{
    otApiDeviceAvailabilityCallback* CallbackToRelease = nullptr;

    EnterCriticalSection(&aApitInstance->CallbackLock);

    if (aApitInstance->DeviceAvailabilityCallbacks != nullptr)
    {
        CallbackToRelease = aApitInstance->DeviceAvailabilityCallbacks;
        aApitInstance->DeviceAvailabilityCallbacks = nullptr;
    }

    if (aCallback != nullptr)
    {
        aApitInstance->DeviceAvailabilityCallbacks = 
            new otApiDeviceAvailabilityCallback(aCallback, aCallbackContext);
    }
    
    LeaveCriticalSection(&aApitInstance->CallbackLock);

    if (CallbackToRelease)
    {
        CallbackToRelease->Release(true);
        delete CallbackToRelease;
    }
}

OTAPI 
otDeviceList* 
OTCALL
otEnumerateDevices(
    _In_ otApiInstance *aApitInstance
    )
{
    DWORD dwError = ERROR_SUCCESS;
    OVERLAPPED Overlapped = { 0 };
    DWORD dwBytesReturned = 0;
    otDeviceList* pDeviceList = nullptr;
    DWORD cbDeviceList = sizeof(otDeviceList);
    
    otLogFuncEntry();

    Overlapped.hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
    if (Overlapped.hEvent == nullptr)
    {
        dwError = GetLastError();
        otLogCritApi("CreateEvent (Overlapped.hEvent) failed, %!WINERROR!", dwError);
        goto error;
    }
    
    pDeviceList = (otDeviceList*)malloc(cbDeviceList);
    if (pDeviceList == nullptr)
    {
        otLogWarnApi("Failed to allocate otDeviceList of %u bytes.", cbDeviceList);
        dwError = ERROR_NOT_ENOUGH_MEMORY;
        goto error;
    }
    RtlZeroMemory(pDeviceList, cbDeviceList);
    
    // Query in a loop to account for it changing between calls
    while (true)
    {
        // Send the IOCTL to query the interfaces
        if (!DeviceIoControl(
                aApitInstance->DeviceHandle,
                IOCTL_OTLWF_ENUMERATE_DEVICES,
                nullptr, 0,
                pDeviceList, cbDeviceList,
                nullptr, 
                &Overlapped))
        {
            dwError = GetLastError();
            if (dwError != ERROR_IO_PENDING)
            {
                otLogCritApi("DeviceIoControl(IOCTL_OTLWF_ENUMERATE_DEVICES) failed, %!WINERROR!", dwError);
                goto error;
            }
            dwError = ERROR_SUCCESS;
        }

        // Get the result of the IO operation
        if (!GetOverlappedResultEx(
                aApitInstance->DeviceHandle,
                &Overlapped,
                &dwBytesReturned,
                c_MaxOverlappedWaitTimeMS,
                TRUE))
        {
            dwError = GetLastError();
            if (dwError == WAIT_TIMEOUT)
            {
                dwError = ERROR_TIMEOUT;
                CancelIoEx(aApitInstance->DeviceHandle, &Overlapped);
            }
            otLogCritApi("GetOverlappedResult for notification failed, %!WINERROR!", dwError);
            goto error;
        }
        
        // Calculate the expected size of the full buffer
        cbDeviceList = 
            FIELD_OFFSET(otDeviceList, aDevices) +
            pDeviceList->aDevicesLength * sizeof(otDeviceList::aDevices);
        
        // Make sure they returned a complete buffer
        if (dwBytesReturned != sizeof(otDeviceList::aDevicesLength)) break;
        
        // If we get here that means we didn't have a big enough buffer
        // Reallocate a new buffer
        free(pDeviceList);
        pDeviceList = (otDeviceList*)malloc(cbDeviceList);
        if (pDeviceList == nullptr)
        {
            otLogCritApi("Failed to allocate otDeviceList of %u bytes.", cbDeviceList);
            dwError = ERROR_NOT_ENOUGH_MEMORY;
            goto error;
        }
        RtlZeroMemory(pDeviceList, cbDeviceList);
    }

error:

    if (dwError != ERROR_SUCCESS)
    {
        free(pDeviceList);
        pDeviceList = nullptr;
    }

    if (Overlapped.hEvent)
    {
        CloseHandle(Overlapped.hEvent);
    }
    
    otLogFuncExitMsg("%d devices", pDeviceList == nullptr ? -1 : (int)pDeviceList->aDevicesLength);

    return pDeviceList;
}
    
OTAPI 
otInstance *
OTCALL
otInstanceInit(
    _In_ otApiInstance *aApitInstance, 
    _In_ const GUID *aDeviceGuid
    )
{
    otInstance *aInstance = nullptr;

    OTLWF_DEVICE Result = {0};
    if (aApitInstance &&
        SendIOCTL(
            aApitInstance, 
            IOCTL_OTLWF_QUERY_DEVICE, 
            (LPVOID)aDeviceGuid, 
            sizeof(GUID), 
            &Result, 
            sizeof(Result)
            ) == ERROR_SUCCESS)
    {
        aInstance = (otInstance*)malloc(sizeof(otInstance));
        if (aInstance)
        {
            aInstance->ApiHandle = aApitInstance;
            aInstance->InterfaceGuid = *aDeviceGuid;
            aInstance->CompartmentID = Result.CompartmentID;

            if (ConvertInterfaceGuidToLuid(aDeviceGuid, &aInstance->InterfaceLuid) != ERROR_SUCCESS ||
                ConvertInterfaceLuidToIndex(&aInstance->InterfaceLuid, &aInstance->InterfaceIndex) != ERROR_SUCCESS)
            {
                otLogCritApi("Failed to convert interface guid to index!");
                free(aInstance);
                aInstance = nullptr;
            }
        }
    }

    return aInstance;
}

OTAPI 
GUID 
OTCALL
otGetDeviceGuid(
    otInstance *aInstance
    )
{
    if (aInstance == nullptr) return {};
    return aInstance->InterfaceGuid;
}

OTAPI 
uint32_t 
OTCALL
otGetDeviceIfIndex(
    otInstance *aInstance
    )
{
    if (aInstance == nullptr) return (uint32_t)-1;
    return aInstance->InterfaceIndex;
}

OTAPI 
uint32_t 
OTCALL
otGetCompartmentId(
    _In_ otInstance *aInstance
    )
{
    if (aInstance == nullptr) return (uint32_t)-1;
    return aInstance->CompartmentID;
}

OTAPI 
const char *
OTCALL
otGetVersionString()
{
    char* szVersion = (char*)malloc(sizeof(c_Version));
    if (szVersion)
    {
        memcpy_s(szVersion, sizeof(c_Version), c_Version, sizeof(c_Version));
    }
    return szVersion;
}

OTAPI 
ThreadError 
OTCALL
otInterfaceUp(
    _In_ otInstance *aInstance
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(SetIOCTL(aInstance, IOCTL_OTLWF_OT_INTERFACE, (BOOLEAN)TRUE));
}

OTAPI 
ThreadError 
OTCALL
otInterfaceDown(
    _In_ otInstance *aInstance
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(SetIOCTL(aInstance, IOCTL_OTLWF_OT_INTERFACE, (BOOLEAN)FALSE));
}

OTAPI 
bool 
OTCALL
otIsInterfaceUp(
    _In_ otInstance *aInstance
    )
{
    BOOLEAN Result = FALSE;
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_INTERFACE, &Result);
    return Result != FALSE;
}

OTAPI 
ThreadError 
OTCALL
otThreadStart(
    _In_ otInstance *aInstance
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(SetIOCTL(aInstance, IOCTL_OTLWF_OT_THREAD, (BOOLEAN)TRUE));
}

OTAPI 
ThreadError 
OTCALL
otThreadStop(
    _In_ otInstance *aInstance
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(SetIOCTL(aInstance, IOCTL_OTLWF_OT_THREAD, (BOOLEAN)FALSE));
}

OTAPI 
bool 
OTCALL
otIsSingleton(
    _In_ otInstance *aInstance
    )
{
    BOOLEAN Result = FALSE;
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_SINGLETON, &Result);
    return Result != FALSE;
}

OTAPI 
ThreadError 
OTCALL
otActiveScan(
    _In_ otInstance *aInstance, 
    uint32_t aScanChannels, 
    uint16_t aScanDuration,
    otHandleActiveScanResult aCallback,
    _In_ void *aCallbackContext
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;

    aInstance->ApiHandle->SetCallback(
        aInstance->ApiHandle->ActiveScanCallbacks,
        aInstance->InterfaceGuid, aCallback, aCallbackContext
        );
    
    PackedBuffer3<GUID,uint32_t,uint16_t> Buffer(aInstance->InterfaceGuid, aScanChannels, aScanDuration);
    return DwordToThreadError(SendIOCTL(aInstance->ApiHandle, IOCTL_OTLWF_OT_ACTIVE_SCAN, &Buffer, sizeof(Buffer), nullptr, 0));
}

OTAPI 
bool 
OTCALL
otIsActiveScanInProgress(
    _In_ otInstance *aInstance
    )
{
    BOOLEAN Result = FALSE;
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_ACTIVE_SCAN, &Result);
    return Result != FALSE;
}

OTAPI 
ThreadError 
OTCALL 
otEnergyScan(
    _In_ otInstance *aInstance, 
    uint32_t aScanChannels, 
    uint16_t aScanDuration,
    _In_ otHandleEnergyScanResult aCallback, 
    _In_ void *aCallbackContext
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;

    aInstance->ApiHandle->SetCallback(
        aInstance->ApiHandle->EnergyScanCallbacks,
        aInstance->InterfaceGuid, aCallback, aCallbackContext
        );
    
    PackedBuffer3<GUID,uint32_t,uint16_t> Buffer(aInstance->InterfaceGuid, aScanChannels, aScanDuration);
    return DwordToThreadError(SendIOCTL(aInstance->ApiHandle, IOCTL_OTLWF_OT_ENERGY_SCAN, &Buffer, sizeof(Buffer), nullptr, 0));
}

OTAPI 
bool 
OTCALL 
otIsEnergyScanInProgress(
    _In_ otInstance *aInstance
    )
{
    BOOLEAN Result = FALSE;
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_ENERGY_SCAN, &Result);
    return Result != FALSE;
}

OTAPI 
ThreadError 
OTCALL
otDiscover(
    _In_ otInstance *aInstance, 
    uint32_t aScanChannels, 
    uint16_t aScanDuration, 
    uint16_t aPanid,
    otHandleActiveScanResult aCallback,
    void *aCallbackContext
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;

    aInstance->ApiHandle->SetCallback(
        aInstance->ApiHandle->DiscoverCallbacks,
        aInstance->InterfaceGuid, aCallback, aCallbackContext
        );

    PackedBuffer4<GUID,uint32_t,uint16_t,uint16_t> Buffer(aInstance->InterfaceGuid, aScanChannels, aScanDuration, aPanid);
    return DwordToThreadError(SendIOCTL(aInstance->ApiHandle, IOCTL_OTLWF_OT_DISCOVER, &Buffer, sizeof(Buffer), nullptr, 0));
}

OTAPI 
bool 
OTCALL
otIsDiscoverInProgress(
    _In_ otInstance *aInstance
    )
{
    BOOLEAN Result = FALSE;
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_DISCOVER, &Result);
    return Result != FALSE;
}

OTAPI 
ThreadError
OTCALL
otSendMacDataRequest(
    _In_ otInstance *aInstance
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    UNREFERENCED_PARAMETER(aInstance);
    return kThreadError_NotImplemented; // TODO
}

OTAPI 
uint8_t 
OTCALL
otGetChannel(
    _In_ otInstance *aInstance
    )
{
    uint8_t Result = 0xFF;
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_CHANNEL, &Result);
    return Result;
}

OTAPI 
ThreadError 
OTCALL
otSetChannel(
    _In_ otInstance *aInstance, 
    uint8_t aChannel
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(SetIOCTL(aInstance, IOCTL_OTLWF_OT_CHANNEL, aChannel));
}

OTAPI 
uint8_t 
OTCALL
otGetMaxAllowedChildren(
    _In_ otInstance *aInstance
    )
{
    uint8_t Result = 0;
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_MAX_CHILDREN, &Result);
    return Result;
}

OTAPI 
ThreadError 
OTCALL
otSetMaxAllowedChildren(
    _In_ otInstance *aInstance, 
    uint8_t aMaxChildren
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(SetIOCTL(aInstance, IOCTL_OTLWF_OT_MAX_CHILDREN, aMaxChildren));
}

OTAPI 
uint32_t 
OTCALL
otGetChildTimeout(
    _In_ otInstance *aInstance
    )
{
    uint32_t Result = 0;
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_CHILD_TIMEOUT, &Result);
    return Result;
}

OTAPI 
void 
OTCALL
otSetChildTimeout(
    _In_ otInstance *aInstance, 
    uint32_t aTimeout
    )
{
    if (aInstance == nullptr) return;
    if (aInstance) (void)SetIOCTL(aInstance, IOCTL_OTLWF_OT_CHILD_TIMEOUT, aTimeout);
}

OTAPI 
const 
uint8_t *
OTCALL
otGetExtendedAddress(
    _In_ otInstance *aInstance
    )
{
    if (aInstance == nullptr) return nullptr;

    otExtAddress *Result = (otExtAddress*)malloc(sizeof(otExtAddress));
    if (Result && QueryIOCTL(aInstance, IOCTL_OTLWF_OT_EXTENDED_ADDRESS, Result) != ERROR_SUCCESS)
    {
        free(Result);
        Result = nullptr;
    }
    return (uint8_t*)Result;
}

OTAPI 
ThreadError 
OTCALL
otSetExtendedAddress(
    _In_ otInstance *aInstance, 
    const otExtAddress *aExtendedAddress
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(SetIOCTL(aInstance, IOCTL_OTLWF_OT_EXTENDED_ADDRESS, aExtendedAddress));
}

OTAPI 
const uint8_t *
OTCALL
otGetExtendedPanId(
    _In_ otInstance *aInstance
    )
{
    if (aInstance == nullptr) return nullptr;

    otExtendedPanId *Result = (otExtendedPanId*)malloc(sizeof(otExtendedPanId));
    if (Result && QueryIOCTL(aInstance, IOCTL_OTLWF_OT_EXTENDED_PANID, Result) != ERROR_SUCCESS)
    {
        free(Result);
        Result = nullptr;
    }
    return (uint8_t*)Result;
}

OTAPI 
void 
OTCALL
otSetExtendedPanId(
    _In_ otInstance *aInstance, 
    const uint8_t *aExtendedPanId
    )
{
    if (aInstance) (void)SetIOCTL(aInstance, IOCTL_OTLWF_OT_EXTENDED_PANID, (const otExtendedPanId*)aExtendedPanId);
}

OTAPI 
void 
OTCALL
otGetFactoryAssignedIeeeEui64(
    _In_ otInstance *aInstance, 
    _Out_ otExtAddress *aEui64
)
{
    if (aInstance == nullptr) return;
    (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_FACTORY_EUI64, aEui64);
}

OTAPI 
void 
OTCALL
otGetHashMacAddress(
    _In_ otInstance *aInstance, 
    _Out_ otExtAddress *aHashMacAddress
    )
{
    if (aInstance == nullptr) return;
    (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_HASH_MAC_ADDRESS, aHashMacAddress);
}

OTAPI 
ThreadError 
OTCALL
otGetLeaderRloc(
    _In_ otInstance *aInstance, 
    _Out_ otIp6Address *aLeaderRloc
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(QueryIOCTL(aInstance, IOCTL_OTLWF_OT_LEADER_RLOC, aLeaderRloc));
}

OTAPI 
otLinkModeConfig 
OTCALL
otGetLinkMode(
    _In_ otInstance *aInstance
    )
{
    otLinkModeConfig Result = {0};
    static_assert(sizeof(otLinkModeConfig) == 4, "The size of otLinkModeConfig should be 4 bytes");
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_LINK_MODE, &Result);
    return Result;
}

OTAPI 
ThreadError 
OTCALL
otSetLinkMode(
    _In_ otInstance *aInstance, 
    otLinkModeConfig aConfig
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    static_assert(sizeof(otLinkModeConfig) == 4, "The size of otLinkModeConfig should be 4 bytes");
    return DwordToThreadError(SetIOCTL(aInstance, IOCTL_OTLWF_OT_LINK_MODE, aConfig));
}

OTAPI 
const uint8_t *
OTCALL
otGetMasterKey(
    _In_ otInstance *aInstance, 
    _Out_ uint8_t *aKeyLength
    )
{
    if (aInstance == nullptr || aKeyLength == nullptr) return nullptr;

    *aKeyLength = 0;

    struct otMasterKeyAndLength
    {
        otMasterKey Key;
        uint8_t Length;
    };
    otMasterKeyAndLength *Result = (otMasterKeyAndLength*)malloc(sizeof(otMasterKeyAndLength));
    if (Result == nullptr) return nullptr;
    if (QueryIOCTL(aInstance, IOCTL_OTLWF_OT_MASTER_KEY, Result) != ERROR_SUCCESS)
    {
        free(Result);
        return nullptr;
    }
    else
    {
        *aKeyLength = Result->Length;
    }
    return (uint8_t*)Result;
}

OTAPI
ThreadError
OTCALL
otSetMasterKey(
    _In_ otInstance *aInstance, 
    const uint8_t *aKey, 
    uint8_t aKeyLength
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    
    BYTE Buffer[sizeof(GUID) + sizeof(otMasterKey) + sizeof(uint8_t)];
    memcpy_s(Buffer, sizeof(Buffer), &aInstance->InterfaceGuid, sizeof(GUID));
    memcpy_s(Buffer + sizeof(GUID), sizeof(Buffer) - sizeof(GUID), aKey, aKeyLength);
    memcpy_s(Buffer + sizeof(GUID) + sizeof(otMasterKey), sizeof(Buffer) - sizeof(GUID) - sizeof(otMasterKey), &aKeyLength, sizeof(aKeyLength));
    
    return DwordToThreadError(SendIOCTL(aInstance->ApiHandle, IOCTL_OTLWF_OT_MASTER_KEY, Buffer, sizeof(Buffer), nullptr, 0));
}

OTAPI 
int8_t 
OTCALL
otGetMaxTransmitPower(
    _In_ otInstance *aInstance
    )
{
    int8_t Result = 0;
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_MAX_TRANSMIT_POWER, &Result);
    return Result;
}

OTAPI 
void 
OTCALL
otSetMaxTransmitPower(
    _In_ otInstance *aInstance, 
    int8_t aPower
    )
{
    if (aInstance) (void)SetIOCTL(aInstance, IOCTL_OTLWF_OT_MAX_TRANSMIT_POWER, aPower);
}

OTAPI
const otIp6Address *
OTCALL
otGetMeshLocalEid(
    _In_ otInstance *aInstance
    )
{
    otIp6Address *Result = (otIp6Address*)malloc(sizeof(otIp6Address));
    if (Result && QueryIOCTL(aInstance, IOCTL_OTLWF_OT_MESH_LOCAL_EID, Result) != ERROR_SUCCESS)
    {
        free(Result);
        Result = nullptr;
    }
    return Result;
}

OTAPI
const uint8_t *
OTCALL
otGetMeshLocalPrefix(
    _In_ otInstance *aInstance
    )
{
    if (aInstance == nullptr) return nullptr;

    otMeshLocalPrefix *Result = (otMeshLocalPrefix*)malloc(sizeof(otMeshLocalPrefix));
    if (Result && QueryIOCTL(aInstance, IOCTL_OTLWF_OT_MESH_LOCAL_PREFIX, Result) != ERROR_SUCCESS)
    {
        free(Result);
        Result = nullptr;
    }
    return (uint8_t*)Result;
}

OTAPI
ThreadError
OTCALL
otSetMeshLocalPrefix(
    _In_ otInstance *aInstance, 
    const uint8_t *aMeshLocalPrefix
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(SetIOCTL(aInstance, IOCTL_OTLWF_OT_MESH_LOCAL_PREFIX, (const otMeshLocalPrefix*)aMeshLocalPrefix));
}

OTAPI
ThreadError
OTCALL
otGetNetworkDataLeader(
    _In_ otInstance *aInstance, 
    bool aStable, 
    _Out_ uint8_t *aData, 
    _Out_ uint8_t *aDataLength
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    UNREFERENCED_PARAMETER(aInstance);
    UNREFERENCED_PARAMETER(aStable);
    UNREFERENCED_PARAMETER(aData);
    UNREFERENCED_PARAMETER(aDataLength);
    return kThreadError_NotImplemented; // TODO
}

OTAPI
ThreadError
OTCALL
otGetNetworkDataLocal(
    _In_ otInstance *aInstance, 
    bool aStable, 
    _Out_ uint8_t *aData, 
    _Out_ uint8_t *aDataLength
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    UNREFERENCED_PARAMETER(aInstance);
    UNREFERENCED_PARAMETER(aStable);
    UNREFERENCED_PARAMETER(aData);
    UNREFERENCED_PARAMETER(aDataLength);
    return kThreadError_NotImplemented; // TODO
}

OTAPI
const char *
OTCALL
otGetNetworkName(
    _In_ otInstance *aInstance
    )
{
    if (aInstance == nullptr) return nullptr;

    otNetworkName *Result = (otNetworkName*)malloc(sizeof(otNetworkName));
    if (Result && QueryIOCTL(aInstance, IOCTL_OTLWF_OT_NETWORK_NAME, Result) != ERROR_SUCCESS)
    {
        free(Result);
        Result = nullptr;
    }
    return (char*)Result;
}

OTAPI
ThreadError
OTCALL
otSetNetworkName(
    _In_ otInstance *aInstance, 
    _In_ const char *aNetworkName
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;

    otNetworkName Buffer = {0};
    strcpy_s(Buffer.m8, sizeof(Buffer), aNetworkName);
    return DwordToThreadError(SetIOCTL(aInstance, IOCTL_OTLWF_OT_NETWORK_NAME, (const otNetworkName*)&Buffer));
}

OTAPI 
ThreadError 
OTCALL
otGetNextOnMeshPrefix(
    _In_ otInstance *aInstance, 
    bool _aLocal, 
    _Inout_ otNetworkDataIterator *aIterator,
    _Out_ otBorderRouterConfig *aConfig
    )
{
    if (aInstance == nullptr || aConfig == nullptr) return kThreadError_InvalidArgs;
    
    BOOLEAN aLocal = _aLocal ? TRUE : FALSE;
    PackedBuffer3<GUID,BOOLEAN,otNetworkDataIterator> InBuffer(aInstance->InterfaceGuid, aLocal, *aIterator);
    BYTE OutBuffer[sizeof(uint8_t) + sizeof(otBorderRouterConfig)];

    ThreadError aError = 
        DwordToThreadError(
            SendIOCTL(
                aInstance->ApiHandle, 
                IOCTL_OTLWF_OT_NEXT_ON_MESH_PREFIX, 
                &InBuffer, sizeof(InBuffer), 
                OutBuffer, sizeof(OutBuffer)));

    if (aError == kThreadError_None)
    {
        memcpy(aIterator, OutBuffer, sizeof(uint8_t));
        memcpy(aConfig, OutBuffer + sizeof(uint8_t), sizeof(otBorderRouterConfig));
    }
    else
    {
        ZeroMemory(aConfig, sizeof(otBorderRouterConfig));
    }

    return aError;
}

OTAPI
otPanId 
OTCALL
otGetPanId(
    _In_ otInstance *aInstance
    )
{
    otPanId Result = {0};
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_PAN_ID, &Result);
    return Result;
}

OTAPI
ThreadError
OTCALL
otSetPanId(
    _In_ otInstance *aInstance, 
    otPanId aPanId
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(SetIOCTL(aInstance, IOCTL_OTLWF_OT_PAN_ID, aPanId));
}

OTAPI
bool 
OTCALL
otIsRouterRoleEnabled(
    _In_ otInstance *aInstance
    )
{
    BOOLEAN Result = {0};
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_ROUTER_ROLL_ENABLED, &Result);
    return Result != FALSE;
}

OTAPI
void 
OTCALL
otSetRouterRoleEnabled(
    _In_ otInstance *aInstance, 
    bool aEnabled
    )
{
    if (aInstance) (void)SetIOCTL(aInstance, IOCTL_OTLWF_OT_ROUTER_ROLL_ENABLED, (BOOLEAN)aEnabled);
}

OTAPI
otShortAddress 
OTCALL
otGetShortAddress(
    _In_ otInstance *aInstance
    )
{
    otShortAddress Result = {0};
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_SHORT_ADDRESS, &Result);
    return Result;
}

BOOL
GetAdapterAddresses(
    PIP_ADAPTER_ADDRESSES * ppIAA
)
{
    PIP_ADAPTER_ADDRESSES pIAA = NULL;
    DWORD len = 0;
    DWORD flags;

    flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER;
    if (GetAdaptersAddresses(AF_INET6, flags, NULL, NULL, &len) != ERROR_BUFFER_OVERFLOW)
        return FALSE;

    pIAA = (PIP_ADAPTER_ADDRESSES)malloc(len);
    if (pIAA) {
        GetAdaptersAddresses(AF_INET6, flags, NULL, pIAA, &len);
        *ppIAA = pIAA;
        return TRUE;
    }
    return FALSE;
}

OTAPI
const otNetifAddress *
OTCALL
otGetUnicastAddresses(
    _In_ otInstance *aInstance
    )
{
    otLogFuncEntry();
    if (aInstance == nullptr)
    {
        otLogFuncExit();
        return nullptr;
    }

    // Put the current thead in the correct compartment
    bool RevertCompartmentOnExit = false;
    ULONG OriginalCompartmentID = GetCurrentThreadCompartmentId();
    if (OriginalCompartmentID != aInstance->CompartmentID)
    {
        DWORD dwError = ERROR_SUCCESS;
        if ((dwError = SetCurrentThreadCompartmentId(aInstance->CompartmentID)) != ERROR_SUCCESS)
        {
            otLogCritApi("SetCurrentThreadCompartmentId failed, %!WINERROR!", dwError);
            return nullptr;
        }
        RevertCompartmentOnExit = true;
    }

    otNetifAddress *addrs = nullptr;
    ULONG AddrCount = 0;

    // Query the current adapter addresses and format them in the proper output format
    PIP_ADAPTER_ADDRESSES pIAAList;
    if (GetAdapterAddresses(&pIAAList))
    {
        // Loop through all the interfaces
        for (auto pIAA = pIAAList; pIAA != nullptr; pIAA = pIAA->Next) 
        {
            // Look for the right interface
            if (pIAA->Ipv6IfIndex != aInstance->InterfaceIndex) continue;

            // Look through all unicast addresses
            for (auto pUnicastAddr = pIAA->FirstUnicastAddress; 
                 pUnicastAddr != nullptr; 
                 pUnicastAddr = pUnicastAddr->Next)
            {
                AddrCount++;
            }

            break;
        }

        // If we didn't find any addresses, just break out
        if (AddrCount == 0) goto error;

        // Allocate the addresses
        addrs = (otNetifAddress*)malloc(AddrCount * sizeof(otNetifAddress));
        if (addrs == nullptr)
        {
            otLogWarnApi("Not enough memory to alloc otNetifAddress array");
            goto error;
        }
        ZeroMemory(addrs, AddrCount * sizeof(otNetifAddress));

        // Initialize the next pointers
        for (ULONG i = 0; i < AddrCount; i++)
        {
            addrs[i].mNext = (i + 1 == AddrCount) ? nullptr : &addrs[i + 1];
        }

        AddrCount = 0;

        // Loop through all the interfaces
        for (auto pIAA = pIAAList; pIAA != nullptr; pIAA = pIAA->Next) 
        {
            // Look for the right interface
            if (pIAA->Ipv6IfIndex != aInstance->InterfaceIndex) continue;

            // Look through all unicast addresses
            for (auto pUnicastAddr = pIAA->FirstUnicastAddress; 
                 pUnicastAddr != nullptr; 
                 pUnicastAddr = pUnicastAddr->Next)
            {
                LPSOCKADDR_IN6 pAddr = (LPSOCKADDR_IN6)pUnicastAddr->Address.lpSockaddr;

                // Copy the necessary parameters
                memcpy(&addrs[AddrCount].mAddress, &pAddr->sin6_addr, sizeof(pAddr->sin6_addr));
                addrs[AddrCount].mPreferred = pUnicastAddr->PreferredLifetime != 0;
                addrs[AddrCount].mValid = pUnicastAddr->ValidLifetime != 0;
                addrs[AddrCount].mPrefixLength = pUnicastAddr->OnLinkPrefixLength;

                AddrCount++;
            }

            break;
        }

    error:
        free(pIAAList);
    }
    else
    {
        otLogCritApi("GetAdapterAddresses failed!");
    }

    // Revert the comparment if necessary
    if (RevertCompartmentOnExit)
    {
        (VOID)SetCurrentThreadCompartmentId(OriginalCompartmentID);
    }
    
    otLogFuncExitMsg("%d addrs", AddrCount);
    return addrs;
}

OTAPI
ThreadError
OTCALL
otAddUnicastAddress(
    _In_ otInstance *aInstance, 
    const otNetifAddress *aAddress
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;

    // Put the current thead in the correct compartment
    bool RevertCompartmentOnExit = false;
    ULONG OriginalCompartmentID = GetCurrentThreadCompartmentId();
    if (OriginalCompartmentID != aInstance->CompartmentID)
    {
        DWORD dwError = ERROR_SUCCESS;
        if ((dwError = SetCurrentThreadCompartmentId(aInstance->CompartmentID)) != ERROR_SUCCESS)
        {
            otLogCritApi("SetCurrentThreadCompartmentId failed, %!WINERROR!", dwError);
            return kThreadError_Failed;
        }
        RevertCompartmentOnExit = true;
    }

    MIB_UNICASTIPADDRESS_ROW newRow;
    InitializeUnicastIpAddressEntry(&newRow);

    newRow.InterfaceIndex = aInstance->InterfaceIndex;
    newRow.InterfaceLuid = aInstance->InterfaceLuid;
    newRow.Address.si_family = AF_INET6;
    newRow.Address.Ipv6.sin6_family = AF_INET6;
        
    static_assert(sizeof(IN6_ADDR) == sizeof(otIp6Address), "Windows and OpenThread IPv6 Addr Structs must be same size");

    memcpy(&newRow.Address.Ipv6.sin6_addr, &aAddress->mAddress, sizeof(IN6_ADDR));
    newRow.OnLinkPrefixLength = aAddress->mPrefixLength;
    newRow.PreferredLifetime = aAddress->mPreferred ? 0xffffffff : 0;
    newRow.ValidLifetime = aAddress->mValid ? 0xffffffff : 0;
    newRow.PrefixOrigin = IpPrefixOriginOther;  // Derived from network XPANID
    newRow.SkipAsSource = FALSE;                // Allow automatic binding to this address (default)

    if (IN6_IS_ADDR_LINKLOCAL(&newRow.Address.Ipv6.sin6_addr))
    {
        newRow.SuffixOrigin = IpSuffixOriginLinkLayerAddress;   // Derived from Extended MAC address
    }
    else
    {
        newRow.SuffixOrigin = IpSuffixOriginRandom;             // Was created randomly
    }

    DWORD dwError = CreateUnicastIpAddressEntry(&newRow);

    // Revert the comparment if necessary
    if (RevertCompartmentOnExit)
    {
        (VOID)SetCurrentThreadCompartmentId(OriginalCompartmentID);
    }

    if (dwError != ERROR_SUCCESS)
    {
        otLogCritApi("CreateUnicastIpAddressEntry failed %!WINERROR!", dwError);
        return kThreadError_Failed;
    }

    return kThreadError_None;
}

OTAPI
ThreadError
OTCALL
otRemoveUnicastAddress(
    _In_ otInstance *aInstance, 
    const otIp6Address *aAddress
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;

    // Put the current thead in the correct compartment
    bool RevertCompartmentOnExit = false;
    ULONG OriginalCompartmentID = GetCurrentThreadCompartmentId();
    if (OriginalCompartmentID != aInstance->CompartmentID)
    {
        DWORD dwError = ERROR_SUCCESS;
        if ((dwError = SetCurrentThreadCompartmentId(aInstance->CompartmentID)) != ERROR_SUCCESS)
        {
            otLogCritApi("SetCurrentThreadCompartmentId failed, %!WINERROR!", dwError);
            return kThreadError_Failed;
        }
        RevertCompartmentOnExit = true;
    }

    MIB_UNICASTIPADDRESS_ROW deleteRow;
    InitializeUnicastIpAddressEntry(&deleteRow);

    deleteRow.InterfaceIndex = aInstance->InterfaceIndex;
    deleteRow.InterfaceLuid = aInstance->InterfaceLuid;
    deleteRow.Address.si_family = AF_INET6;

    memcpy(&deleteRow.Address.Ipv6.sin6_addr, aAddress, sizeof(IN6_ADDR));
    
    DWORD dwError = DeleteUnicastIpAddressEntry(&deleteRow);

    // Revert the comparment if necessary
    if (RevertCompartmentOnExit)
    {
        (VOID)SetCurrentThreadCompartmentId(OriginalCompartmentID);
    }

    if (dwError != ERROR_SUCCESS)
    {
        otLogCritApi("DeleteUnicastIpAddressEntry failed %!WINERROR!", dwError);
        return kThreadError_Failed;
    }

    return kThreadError_None;
}

OTAPI
ThreadError 
OTCALL
otSetStateChangedCallback(
    _In_ otInstance *aInstance, 
    _In_ otStateChangedCallback aCallback, 
    _In_ void *aContext
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    bool success = 
        aInstance->ApiHandle->SetCallback(
            aInstance->ApiHandle->StateChangedCallbacks,
            aInstance->InterfaceGuid, aCallback, aContext
            );
    return success ? kThreadError_None : kThreadError_Already;
}

OTAPI
void
OTCALL
otRemoveStateChangeCallback(
    _In_ otInstance *aInstance,
    _In_ otStateChangedCallback /* aCallback */,
    _In_ void *aContext
    )
{
    if (aInstance == nullptr) return;
    aInstance->ApiHandle->SetCallback(
        aInstance->ApiHandle->StateChangedCallbacks,
        aInstance->InterfaceGuid, (otStateChangedCallback)nullptr, aContext
        );
}

OTAPI
ThreadError
OTCALL
otGetActiveDataset(
    _In_ otInstance *aInstance, 
    _Out_ otOperationalDataset *aDataset
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(QueryIOCTL(aInstance, IOCTL_OTLWF_OT_ACTIVE_DATASET, aDataset));
}

OTAPI
ThreadError
OTCALL
otSetActiveDataset(
    _In_ otInstance *aInstance, 
    const otOperationalDataset *aDataset
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(SetIOCTL(aInstance, IOCTL_OTLWF_OT_ACTIVE_DATASET, aDataset));
}

OTAPI
ThreadError
OTCALL
otGetPendingDataset(
    _In_ otInstance *aInstance, 
    _Out_ otOperationalDataset *aDataset
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(QueryIOCTL(aInstance, IOCTL_OTLWF_OT_PENDING_DATASET, aDataset));
}

OTAPI
ThreadError
OTCALL
otSetPendingDataset(
    _In_ otInstance *aInstance, 
    const otOperationalDataset *aDataset
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(SetIOCTL(aInstance, IOCTL_OTLWF_OT_PENDING_DATASET, aDataset));
}

OTAPI 
ThreadError 
OTCALL
otSendActiveGet(
    _In_ otInstance *aInstance, 
    const uint8_t *aTlvTypes, 
    uint8_t aLength,
    _In_opt_ const otIp6Address *aAddress
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    if (aTlvTypes == nullptr && aLength != 0) return kThreadError_InvalidArgs;
    
    DWORD BufferSize = sizeof(GUID) + sizeof(uint8_t) + aLength;
    if (aAddress) BufferSize += sizeof(otIp6Address);
    PBYTE Buffer = (PBYTE)malloc(BufferSize);
    if (Buffer == nullptr) return kThreadError_NoBufs;

    memcpy_s(Buffer, BufferSize, &aInstance->InterfaceGuid, sizeof(GUID));
    memcpy_s(Buffer + sizeof(GUID), BufferSize - sizeof(GUID), &aLength, sizeof(aLength));
    if (aLength > 0)
        memcpy_s(Buffer + sizeof(GUID) + sizeof(uint8_t), BufferSize - sizeof(GUID) - sizeof(uint8_t), aTlvTypes, aLength);
    if (aAddress)
        memcpy_s(Buffer + sizeof(GUID) + sizeof(uint8_t) + aLength, BufferSize - sizeof(GUID) - sizeof(uint8_t) - aLength, aAddress, sizeof(otIp6Address));
    
    ThreadError result = 
        DwordToThreadError(SendIOCTL(aInstance->ApiHandle, IOCTL_OTLWF_OT_SEND_ACTIVE_GET, Buffer, BufferSize, nullptr, 0));

    free(Buffer);
    return result;
}

OTAPI 
ThreadError 
OTCALL
otSendActiveSet(
    _In_ otInstance *aInstance, 
    const otOperationalDataset *aDataset, 
    const uint8_t *aTlvs,
    uint8_t aLength
    )
{
    if (aInstance == nullptr || aDataset == nullptr) return kThreadError_InvalidArgs;
    if (aTlvs == nullptr && aLength != 0) return kThreadError_InvalidArgs;
    
    DWORD BufferSize = sizeof(GUID) + sizeof(otOperationalDataset) + sizeof(uint8_t) + aLength;
    PBYTE Buffer = (PBYTE)malloc(BufferSize);
    if (Buffer == nullptr) return kThreadError_NoBufs;

    memcpy_s(Buffer, BufferSize, &aInstance->InterfaceGuid, sizeof(GUID));
    memcpy_s(Buffer + sizeof(GUID), BufferSize - sizeof(GUID), aDataset, sizeof(otOperationalDataset));
    memcpy_s(Buffer + sizeof(GUID) + sizeof(otOperationalDataset), BufferSize - sizeof(GUID) - sizeof(otOperationalDataset), &aLength, sizeof(aLength));
    if (aLength > 0)
        memcpy_s(Buffer + sizeof(GUID) + sizeof(otOperationalDataset) + sizeof(uint8_t), BufferSize - sizeof(GUID) - sizeof(otOperationalDataset) - sizeof(uint8_t), aTlvs, aLength);
    
    ThreadError result = 
        DwordToThreadError(SendIOCTL(aInstance->ApiHandle, IOCTL_OTLWF_OT_SEND_ACTIVE_SET, Buffer, BufferSize, nullptr, 0));

    free(Buffer);
    return result;
}

OTAPI 
ThreadError 
OTCALL
otSendPendingGet(
    _In_ otInstance *aInstance, 
    const uint8_t *aTlvTypes, 
    uint8_t aLength,
    _In_opt_ const otIp6Address *aAddress
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    if (aTlvTypes == nullptr && aLength != 0) return kThreadError_InvalidArgs;
    
    DWORD BufferSize = sizeof(GUID) + sizeof(uint8_t) + aLength;
    if (aAddress) BufferSize += sizeof(otIp6Address);
    PBYTE Buffer = (PBYTE)malloc(BufferSize);
    if (Buffer == nullptr) return kThreadError_NoBufs;

    memcpy_s(Buffer, BufferSize, &aInstance->InterfaceGuid, sizeof(GUID));
    memcpy_s(Buffer + sizeof(GUID), BufferSize - sizeof(GUID), &aLength, sizeof(aLength));
    if (aLength > 0)
        memcpy_s(Buffer + sizeof(GUID) + sizeof(uint8_t), BufferSize - sizeof(GUID) - sizeof(uint8_t), aTlvTypes, aLength);
    if (aAddress)
        memcpy_s(Buffer + sizeof(GUID) + sizeof(uint8_t) + aLength, BufferSize - sizeof(GUID) - sizeof(uint8_t) - aLength, aAddress, sizeof(otIp6Address));
    
    ThreadError result = 
        DwordToThreadError(SendIOCTL(aInstance->ApiHandle, IOCTL_OTLWF_OT_SEND_PENDING_GET, Buffer, BufferSize, nullptr, 0));

    free(Buffer);
    return result;
}

OTAPI 
ThreadError 
OTCALL
otSendPendingSet(
    _In_ otInstance *aInstance, 
    const otOperationalDataset *aDataset, 
    const uint8_t *aTlvs,
    uint8_t aLength
    )
{
    if (aInstance == nullptr || aDataset == nullptr) return kThreadError_InvalidArgs;
    if (aTlvs == nullptr && aLength != 0) return kThreadError_InvalidArgs;
    
    DWORD BufferSize = sizeof(GUID) + sizeof(otOperationalDataset) + sizeof(uint8_t) + aLength;
    PBYTE Buffer = (PBYTE)malloc(BufferSize);
    if (Buffer == nullptr) return kThreadError_NoBufs;

    memcpy_s(Buffer, BufferSize, &aInstance->InterfaceGuid, sizeof(GUID));
    memcpy_s(Buffer + sizeof(GUID), BufferSize - sizeof(GUID), aDataset, sizeof(otOperationalDataset));
    memcpy_s(Buffer + sizeof(GUID) + sizeof(otOperationalDataset), BufferSize - sizeof(GUID) - sizeof(otOperationalDataset), &aLength, sizeof(aLength));
    if (aLength > 0)
        memcpy_s(Buffer + sizeof(GUID) + sizeof(otOperationalDataset) + sizeof(uint8_t), BufferSize - sizeof(GUID) - sizeof(otOperationalDataset) - sizeof(uint8_t), aTlvs, aLength);
    
    ThreadError result = 
        DwordToThreadError(SendIOCTL(aInstance->ApiHandle, IOCTL_OTLWF_OT_SEND_PENDING_SET, Buffer, BufferSize, nullptr, 0));

    free(Buffer);
    return result;
}

OTAPI 
uint32_t 
OTCALL
otGetPollPeriod(
    _In_ otInstance *aInstance
    )
{
    uint32_t Result = 0;
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_POLL_PERIOD, &Result);
    return Result;
}

OTAPI 
void 
OTCALL
otSetPollPeriod(
    _In_ otInstance *aInstance, 
    uint32_t aPollPeriod
    )
{
    if (aInstance) (void)SetIOCTL(aInstance, IOCTL_OTLWF_OT_POLL_PERIOD, aPollPeriod);
}

OTAPI
uint8_t 
OTCALL
otGetLocalLeaderWeight(
    _In_ otInstance *aInstance
    )
{
    uint8_t Result = 0;
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_LOCAL_LEADER_WEIGHT, &Result);
    return Result;
}

OTAPI
void 
OTCALL
otSetLocalLeaderWeight(
    _In_ otInstance *aInstance, 
    uint8_t aWeight
    )
{
    if (aInstance) (void)SetIOCTL(aInstance, IOCTL_OTLWF_OT_LOCAL_LEADER_WEIGHT, aWeight);
}

OTAPI 
uint32_t 
OTCALL
otGetLocalLeaderPartitionId(
    _In_ otInstance *aInstance
    )
{
    uint32_t Result = 0;
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_LOCAL_LEADER_PARTITION_ID, &Result);
    return Result;
}

OTAPI 
void 
OTCALL
otSetLocalLeaderPartitionId(
    _In_ otInstance *aInstance, 
    uint32_t aPartitionId
    )
{
    if (aInstance) (void)SetIOCTL(aInstance, IOCTL_OTLWF_OT_LOCAL_LEADER_PARTITION_ID, aPartitionId);
}

OTAPI 
uint16_t 
OTCALL 
otGetJoinerUdpPort(
    _In_ otInstance *aInstance
)
{
    uint16_t Result = 0;
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_JOINER_UDP_PORT, &Result);
    return Result;
}

OTAPI 
ThreadError 
OTCALL 
otSetJoinerUdpPort(
    _In_ otInstance *aInstance, 
    uint16_t aJoinerUdpPort
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(SetIOCTL(aInstance, IOCTL_OTLWF_OT_JOINER_UDP_PORT, aJoinerUdpPort));
}

OTAPI
ThreadError
OTCALL
otAddBorderRouter(
    _In_ otInstance *aInstance, 
    const otBorderRouterConfig *aConfig
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(SetIOCTL(aInstance, IOCTL_OTLWF_OT_ADD_BORDER_ROUTER, aConfig));
}

OTAPI
ThreadError
OTCALL
otRemoveBorderRouter(
    _In_ otInstance *aInstance, 
    const otIp6Prefix *aPrefix
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(SetIOCTL(aInstance, IOCTL_OTLWF_OT_REMOVE_BORDER_ROUTER, aPrefix));
}

OTAPI
ThreadError
OTCALL
otAddExternalRoute(
    _In_ otInstance *aInstance, 
    const otExternalRouteConfig *aConfig
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(SetIOCTL(aInstance, IOCTL_OTLWF_OT_ADD_EXTERNAL_ROUTE, aConfig));
}

OTAPI
ThreadError
OTCALL
otRemoveExternalRoute(
    _In_ otInstance *aInstance, 
    const otIp6Prefix *aPrefix
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(SetIOCTL(aInstance, IOCTL_OTLWF_OT_REMOVE_EXTERNAL_ROUTE, aPrefix));
}

OTAPI
ThreadError
OTCALL
otSendServerData(
    _In_ otInstance *aInstance
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(SetIOCTL(aInstance, IOCTL_OTLWF_OT_SEND_SERVER_DATA));
}

OTAPI
uint32_t 
OTCALL
otGetContextIdReuseDelay(
    _In_ otInstance *aInstance
    )
{
    uint32_t Result = 0;
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_CONTEXT_ID_REUSE_DELAY, &Result);
    return Result;
}

OTAPI
void 
OTCALL
otSetContextIdReuseDelay(
    _In_ otInstance *aInstance, 
    uint32_t aDelay
    )
{
    if (aInstance) (void)SetIOCTL(aInstance, IOCTL_OTLWF_OT_CONTEXT_ID_REUSE_DELAY, aDelay);
}

OTAPI
uint32_t 
OTCALL
otGetKeySequenceCounter(
    _In_ otInstance *aInstance
    )
{
    uint32_t Result = 0;
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_KEY_SEQUENCE_COUNTER, &Result);
    return Result;
}

OTAPI
void 
OTCALL
otSetKeySequenceCounter(
    _In_ otInstance *aInstance, 
    uint32_t aKeySequenceCounter
    )
{
    if (aInstance) (void)SetIOCTL(aInstance, IOCTL_OTLWF_OT_KEY_SEQUENCE_COUNTER, aKeySequenceCounter);
}

OTAPI
uint32_t 
OTCALL
otGetKeySwitchGuardTime(
    _In_ otInstance *aInstance
    )
{
    uint32_t Result = 0;
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_KEY_SWITCH_GUARDTIME, &Result);
    return Result;
}

OTAPI
void 
OTCALL
otSetKeySwitchGuardTime(
    _In_ otInstance *aInstance, 
    uint32_t aKeySwitchGuardTime
    )
{
    if (aInstance) (void)SetIOCTL(aInstance, IOCTL_OTLWF_OT_KEY_SWITCH_GUARDTIME, aKeySwitchGuardTime);
}

OTAPI
uint8_t
OTCALL
otGetNetworkIdTimeout(
    _In_ otInstance *aInstance
    )
{
    uint8_t Result = 0;
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_NETWORK_ID_TIMEOUT, &Result);
    return Result;
}

OTAPI
void 
OTCALL
otSetNetworkIdTimeout(
    _In_ otInstance *aInstance, 
    uint8_t aTimeout
    )
{
    if (aInstance) (void)SetIOCTL(aInstance, IOCTL_OTLWF_OT_NETWORK_ID_TIMEOUT, aTimeout);
}

OTAPI
uint8_t 
OTCALL
otGetRouterUpgradeThreshold(
    _In_ otInstance *aInstance
    )
{
    uint8_t Result = 0;
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_ROUTER_UPGRADE_THRESHOLD, &Result);
    return Result;
}

OTAPI
void 
OTCALL
otSetRouterUpgradeThreshold(
    _In_ otInstance *aInstance, 
    uint8_t aThreshold
    )
{
    if (aInstance) (void)SetIOCTL(aInstance, IOCTL_OTLWF_OT_ROUTER_UPGRADE_THRESHOLD, aThreshold);
}

OTAPI 
uint8_t 
OTCALL
otGetRouterDowngradeThreshold(
    _In_ otInstance *aInstance
    )
{
    uint8_t Result = 0;
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_ROUTER_DOWNGRADE_THRESHOLD, &Result);
    return Result;
}

OTAPI 
void 
OTCALL
otSetRouterDowngradeThreshold(
    _In_ otInstance *aInstance, 
    uint8_t aThreshold
    )
{
    if (aInstance) (void)SetIOCTL(aInstance, IOCTL_OTLWF_OT_ROUTER_DOWNGRADE_THRESHOLD, aThreshold);
}

OTAPI 
uint8_t 
OTCALL
otGetRouterSelectionJitter(
    _In_ otInstance *aInstance
    )
{
    uint8_t Result = 0;
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_ROUTER_SELECTION_JITTER, &Result);
    return Result;
}

OTAPI 
void 
OTCALL
otSetRouterSelectionJitter(
    _In_ otInstance *aInstance, 
    uint8_t aRouterJitter
    )
{
    if (aInstance) (void)SetIOCTL(aInstance, IOCTL_OTLWF_OT_ROUTER_SELECTION_JITTER, aRouterJitter);
}

OTAPI
ThreadError
OTCALL
otReleaseRouterId(
    _In_ otInstance *aInstance, 
    uint8_t aRouterId
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(SetIOCTL(aInstance, IOCTL_OTLWF_OT_RELEASE_ROUTER_ID, aRouterId));
}

OTAPI
ThreadError
OTCALL
otAddMacWhitelist(
    _In_ otInstance *aInstance, 
    const uint8_t *aExtAddr
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(SetIOCTL(aInstance, IOCTL_OTLWF_OT_ADD_MAC_WHITELIST, (const otExtAddress*)aExtAddr));
}

OTAPI
ThreadError
OTCALL
otAddMacWhitelistRssi(
    _In_ otInstance *aInstance, 
    const uint8_t *aExtAddr, 
    int8_t aRssi
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    
    PackedBuffer3<GUID,otExtAddress,int8_t> Buffer(aInstance->InterfaceGuid, *(otExtAddress*)aExtAddr, aRssi);
    return DwordToThreadError(SendIOCTL(aInstance->ApiHandle, IOCTL_OTLWF_OT_ADD_MAC_WHITELIST, &Buffer, sizeof(Buffer), nullptr, 0));
}

OTAPI
void 
OTCALL
otRemoveMacWhitelist(
    _In_ otInstance *aInstance, 
    const uint8_t *aExtAddr
    )
{
    if (aInstance) (void)SetIOCTL(aInstance, IOCTL_OTLWF_OT_REMOVE_MAC_WHITELIST, (const otExtAddress*)aExtAddr);
}

OTAPI
ThreadError
OTCALL
otGetMacWhitelistEntry(
    _In_ otInstance *aInstance, 
    uint8_t aIndex, 
    _Out_ otMacWhitelistEntry *aEntry
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(QueryIOCTL(aInstance, IOCTL_OTLWF_OT_MAC_WHITELIST_ENTRY, &aIndex, aEntry));
}

OTAPI
void 
OTCALL
otClearMacWhitelist(
    _In_ otInstance *aInstance
    )
{
    if (aInstance) (void)SetIOCTL(aInstance, IOCTL_OTLWF_OT_CLEAR_MAC_WHITELIST);
}

OTAPI
void 
OTCALL
otDisableMacWhitelist(
    _In_ otInstance *aInstance
    )
{
    if (aInstance) (void)SetIOCTL(aInstance, IOCTL_OTLWF_OT_MAC_WHITELIST_ENABLED, (BOOLEAN)FALSE);
}

OTAPI
void 
OTCALL
otEnableMacWhitelist(
    _In_ otInstance *aInstance
    )
{
    if (aInstance) (void)SetIOCTL(aInstance, IOCTL_OTLWF_OT_MAC_WHITELIST_ENABLED, (BOOLEAN)TRUE);
}

OTAPI
bool 
OTCALL
otIsMacWhitelistEnabled(
    _In_ otInstance *aInstance
    )
{
    BOOLEAN Result = 0;
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_MAC_WHITELIST_ENABLED, &Result);
    return Result != FALSE;
}

OTAPI
ThreadError
OTCALL
otBecomeDetached(
    _In_ otInstance *aInstance
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(SetIOCTL(aInstance, IOCTL_OTLWF_OT_DEVICE_ROLE, (uint8_t)kDeviceRoleDetached));
}

OTAPI
ThreadError
OTCALL
otBecomeChild(
    _In_ otInstance *aInstance, 
    otMleAttachFilter aFilter
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;

    uint8_t Role = kDeviceRoleDetached;
    uint8_t Filter = (uint8_t)aFilter;

    PackedBuffer3<GUID,uint8_t,uint8_t> Buffer(aInstance->InterfaceGuid, Role, Filter);
    return DwordToThreadError(SendIOCTL(aInstance->ApiHandle, IOCTL_OTLWF_OT_DEVICE_ROLE, &Buffer, sizeof(Buffer), nullptr, 0));
}

OTAPI
ThreadError
OTCALL
otBecomeRouter(
    _In_ otInstance *aInstance
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(SetIOCTL(aInstance, IOCTL_OTLWF_OT_DEVICE_ROLE, (uint8_t)kDeviceRoleRouter));
}

OTAPI
ThreadError
OTCALL
otBecomeLeader(
    _In_ otInstance *aInstance
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(SetIOCTL(aInstance, IOCTL_OTLWF_OT_DEVICE_ROLE, (uint8_t)kDeviceRoleLeader));
}

OTAPI
ThreadError
OTCALL
otAddMacBlacklist(
    _In_ otInstance *aInstance, 
    const uint8_t *aExtAddr
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(SetIOCTL(aInstance, IOCTL_OTLWF_OT_ADD_MAC_BLACKLIST, (const otExtAddress*)aExtAddr));
}

OTAPI
void 
OTCALL
otRemoveMacBlacklist(
    _In_ otInstance *aInstance, 
    const uint8_t *aExtAddr
    )
{
    if (aInstance) (void)SetIOCTL(aInstance, IOCTL_OTLWF_OT_REMOVE_MAC_BLACKLIST, (const otExtAddress*)aExtAddr);
}

OTAPI
ThreadError
OTCALL
otGetMacBlacklistEntry(
    _In_ otInstance *aInstance, 
    uint8_t aIndex, 
    _Out_ otMacBlacklistEntry *aEntry
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(QueryIOCTL(aInstance, IOCTL_OTLWF_OT_MAC_BLACKLIST_ENTRY, &aIndex, aEntry));
}

OTAPI
void 
OTCALL
otClearMacBlacklist(
    _In_ otInstance *aInstance
    )
{
    if (aInstance) (void)SetIOCTL(aInstance, IOCTL_OTLWF_OT_CLEAR_MAC_BLACKLIST);
}

OTAPI
void 
OTCALL
otDisableMacBlacklist(
    _In_ otInstance *aInstance
    )
{
    if (aInstance) (void)SetIOCTL(aInstance, IOCTL_OTLWF_OT_MAC_BLACKLIST_ENABLED, (BOOLEAN)FALSE);
}

OTAPI
void 
OTCALL
otEnableMacBlacklist(
    _In_ otInstance *aInstance
    )
{
    if (aInstance) (void)SetIOCTL(aInstance, IOCTL_OTLWF_OT_MAC_BLACKLIST_ENABLED, (BOOLEAN)TRUE);
}

OTAPI
bool 
OTCALL
otIsMacBlacklistEnabled(
    _In_ otInstance *aInstance
    )
{
    BOOLEAN Result = 0;
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_MAC_BLACKLIST_ENABLED, &Result);
    return Result != FALSE;
}

OTAPI 
ThreadError 
OTCALL
otGetAssignLinkQuality(
    _In_ otInstance *aInstance, 
    const uint8_t *aExtAddr, 
    _Out_ uint8_t *aLinkQuality
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(QueryIOCTL(aInstance, IOCTL_OTLWF_OT_ASSIGN_LINK_QUALITY, (otExtAddress*)aExtAddr, aLinkQuality));
}

OTAPI 
void 
OTCALL
otSetAssignLinkQuality(
    _In_ otInstance *aInstance,
    const uint8_t *aExtAddr, 
    uint8_t aLinkQuality
    )
{
    if (aInstance == nullptr) return;
    PackedBuffer3<GUID,otExtAddress,uint8_t> Buffer(aInstance->InterfaceGuid, *(otExtAddress*)aExtAddr, aLinkQuality);
    (void)SendIOCTL(aInstance->ApiHandle, IOCTL_OTLWF_OT_ASSIGN_LINK_QUALITY, &Buffer, sizeof(Buffer), nullptr, 0);
}

OTAPI 
void 
OTCALL
otPlatformReset(
    _In_ otInstance *aInstance
    )
{
    if (aInstance) (void)SetIOCTL(aInstance, IOCTL_OTLWF_OT_PLATFORM_RESET);
}

OTAPI 
void 
OTCALL
otFactoryReset(
    _In_ otInstance *aInstance
    )
{
    if (aInstance) (void)SetIOCTL(aInstance, IOCTL_OTLWF_OT_FACTORY_RESET);
}

OTAPI
ThreadError
OTCALL
otGetChildInfoById(
    _In_ otInstance *aInstance, 
    uint16_t aChildId, 
    _Out_ otChildInfo *aChildInfo
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(QueryIOCTL(aInstance, IOCTL_OTLWF_OT_CHILD_INFO_BY_ID, &aChildId, aChildInfo));
}

OTAPI
ThreadError
OTCALL
otGetChildInfoByIndex(
    _In_ otInstance *aInstance, 
    uint8_t aChildIndex, 
    _Out_ otChildInfo *aChildInfo
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(QueryIOCTL(aInstance, IOCTL_OTLWF_OT_CHILD_INFO_BY_INDEX, &aChildIndex, aChildInfo));
}

OTAPI
otDeviceRole 
OTCALL
otGetDeviceRole(
    _In_ otInstance *aInstance
    )
{
    uint8_t Result = kDeviceRoleOffline;
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_DEVICE_ROLE, &Result);
    return (otDeviceRole)Result;
}

OTAPI
ThreadError
OTCALL
otGetEidCacheEntry(
    _In_ otInstance *aInstance, 
    uint8_t aIndex, 
    _Out_ otEidCacheEntry *aEntry
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(QueryIOCTL(aInstance, IOCTL_OTLWF_OT_EID_CACHE_ENTRY, &aIndex, aEntry));
}

OTAPI
ThreadError
OTCALL
otGetLeaderData(
    _In_ otInstance *aInstance, 
    _Out_ otLeaderData *aLeaderData
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(QueryIOCTL(aInstance, IOCTL_OTLWF_OT_LEADER_DATA, aLeaderData));
}

OTAPI
uint8_t 
OTCALL
otGetLeaderRouterId(
    _In_ otInstance *aInstance
    )
{
    uint8_t Result = 0xFF;
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_LEADER_ROUTER_ID, &Result);
    return Result;
}

OTAPI
uint8_t 
OTCALL
otGetLeaderWeight(
    _In_ otInstance *aInstance
    )
{
    uint8_t Result = 0xFF;
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_LEADER_WEIGHT, &Result);
    return Result;
}

OTAPI
uint8_t 
OTCALL
otGetNetworkDataVersion(
    _In_ otInstance *aInstance
    )
{
    uint8_t Result = 0xFF;
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_NETWORK_DATA_VERSION, &Result);
    return Result;
}

OTAPI
uint32_t 
OTCALL
otGetPartitionId(
    _In_ otInstance *aInstance
    )
{
    uint32_t Result = 0xFFFFFFFF;
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_PARTITION_ID, &Result);
    return Result;
}

OTAPI
uint16_t 
OTCALL
otGetRloc16(
    _In_ otInstance *aInstance
    )
{
    uint16_t Result = 0xFFFF;
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_RLOC16, &Result);
    return Result;
}

OTAPI
uint8_t 
OTCALL
otGetRouterIdSequence(
    _In_ otInstance *aInstance
    )
{
    uint8_t Result = 0xFF;
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_ROUTER_ID_SEQUENCE, &Result);
    return Result;
}

OTAPI
ThreadError
OTCALL
otGetRouterInfo(
    _In_ otInstance *aInstance, 
    uint16_t aRouterId, 
    _Out_ otRouterInfo *aRouterInfo
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(QueryIOCTL(aInstance, IOCTL_OTLWF_OT_ROUTER_INFO, &aRouterId, aRouterInfo));
}

OTAPI 
ThreadError 
OTCALL
otGetParentInfo(
    _In_ otInstance *aInstance, 
    _Out_ otRouterInfo *aParentInfo
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    static_assert(sizeof(otRouterInfo) == 20, "The size of otRouterInfo should be 20 bytes");
    return DwordToThreadError(QueryIOCTL(aInstance, IOCTL_OTLWF_OT_PARENT_INFO, aParentInfo));
}

OTAPI
uint8_t 
OTCALL
otGetStableNetworkDataVersion(
    _In_ otInstance *aInstance
    )
{
    uint8_t Result = 0xFF;
    if (aInstance) (void)QueryIOCTL(aInstance, IOCTL_OTLWF_OT_STABLE_NETWORK_DATA_VERSION, &Result);
    return Result;
}

OTAPI
const otMacCounters*
OTCALL
otGetMacCounters(
    _In_ otInstance *aInstance
    )
{
    if (aInstance == nullptr) return nullptr;

    otMacCounters* aCounters = (otMacCounters*)malloc(sizeof(otMacCounters));
    if (aCounters)
    {
        if (ERROR_SUCCESS != QueryIOCTL(aInstance, IOCTL_OTLWF_OT_MAC_COUNTERS, aCounters))
        {
            free(aCounters);
            aCounters = nullptr;
        }
    }
    return aCounters;
}

OTAPI
void
OTCALL
otGetMessageBufferInfo(
    _In_ otInstance *,
    _Out_ otBufferInfo *aBufferInfo
    )
{
    // Not supported on Windows
    ZeroMemory(aBufferInfo, sizeof(otBufferInfo));
}

OTAPI
bool 
OTCALL
otIsIp6AddressEqual(
    const otIp6Address *a, 
    const otIp6Address *b
    )
{
    return memcmp(a->mFields.m8, b->mFields.m8, sizeof(otIp6Address)) == 0;
}

OTAPI
ThreadError 
OTCALL
otIp6AddressFromString(
    const char *str, 
    otIp6Address *address
    )
{
    ThreadError error = kThreadError_None;
    uint8_t *dst = reinterpret_cast<uint8_t *>(address->mFields.m8);
    uint8_t *endp = reinterpret_cast<uint8_t *>(address->mFields.m8 + 15);
    uint8_t *colonp = NULL;
    uint16_t val = 0;
    uint8_t count = 0;
    bool first = true;
    char ch;
    uint8_t d;

    memset(address->mFields.m8, 0, 16);

    dst--;

    for (;;)
    {
        ch = *str++;
        d = ch & 0xf;

        if (('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F'))
        {
            d += 9;
        }
        else if (ch == ':' || ch == '\0' || ch == ' ')
        {
            if (count)
            {
                if (dst + 2 > endp)
                {
                    error = kThreadError_Parse;
                    goto exit;
                }
                *(dst + 1) = static_cast<uint8_t>(val >> 8);
                *(dst + 2) = static_cast<uint8_t>(val);
                dst += 2;
                count = 0;
                val = 0;
            }
            else if (ch == ':')
            {
                if (!(colonp == nullptr || first))
                {
                    error = kThreadError_Parse;
                    goto exit;
                }
                colonp = dst;
            }

            if (ch == '\0' || ch == ' ')
            {
                break;
            }

            continue;
        }
        else
        {
            if (!('0' <= ch && ch <= '9'))
            {
                error = kThreadError_Parse;
                goto exit;
            }
        }

        first = false;
        val = static_cast<uint16_t>((val << 4) | d);
        if (!(++count <= 4))
        {
            error = kThreadError_Parse;
            goto exit;
        }
    }

    while (colonp && dst > colonp)
    {
        *endp-- = *dst--;
    }

    while (endp > dst)
    {
        *endp-- = 0;
    }

exit:
    return error;
}

OTAPI 
uint8_t 
OTCALL
otIp6PrefixMatch(
    const otIp6Address *aFirst, 
    const otIp6Address *aSecond
    )
{
    uint8_t rval = 0;
    uint8_t diff;

    for (uint8_t i = 0; i < sizeof(otIp6Address); i++)
    {
        diff = aFirst->mFields.m8[i] ^ aSecond->mFields.m8[i];

        if (diff == 0)
        {
            rval += 8;
        }
        else
        {
            while ((diff & 0x80) == 0)
            {
                rval++;
                diff <<= 1;
            }

            break;
        }
    }

    return rval;
}

OTAPI 
ThreadError 
OTCALL 
otSendDiagnosticGet(
    _In_ otInstance *aInstance, 
    const otIp6Address *aDestination, 
    const uint8_t aTlvTypes[],
    uint8_t aCount
    )
{
    if (aInstance == nullptr || aDestination == nullptr) return kThreadError_InvalidArgs;
    if (aTlvTypes == nullptr && aCount != 0) return kThreadError_InvalidArgs;
    
    DWORD BufferSize = sizeof(GUID) + sizeof(otIp6Address) + sizeof(uint8_t) + aCount;
    PBYTE Buffer = (PBYTE)malloc(BufferSize);
    if (Buffer == nullptr) return kThreadError_NoBufs;

    memcpy_s(Buffer, BufferSize, &aInstance->InterfaceGuid, sizeof(GUID));
    memcpy_s(Buffer + sizeof(GUID), BufferSize - sizeof(GUID), aDestination, sizeof(otIp6Address));
    memcpy_s(Buffer + sizeof(GUID) + sizeof(otIp6Address), BufferSize - sizeof(GUID) - sizeof(otIp6Address), &aCount, sizeof(aCount));
    memcpy_s(Buffer + sizeof(GUID) + sizeof(otIp6Address) + sizeof(uint8_t), BufferSize - sizeof(GUID) - sizeof(otIp6Address) - sizeof(uint8_t), aTlvTypes, aCount);
    
    ThreadError result = 
        DwordToThreadError(SendIOCTL(aInstance->ApiHandle, IOCTL_OTLWF_OT_SEND_DIAGNOSTIC_GET, Buffer, BufferSize, nullptr, 0));

    free(Buffer);
    return result;
}

OTAPI 
ThreadError 
OTCALL 
otSendDiagnosticReset(
    _In_ otInstance *aInstance, 
    const otIp6Address *aDestination, 
    const uint8_t aTlvTypes[],
    uint8_t aCount
    )
{
    if (aInstance == nullptr || aDestination == nullptr) return kThreadError_InvalidArgs;
    if (aTlvTypes == nullptr && aCount != 0) return kThreadError_InvalidArgs;
    
    DWORD BufferSize = sizeof(GUID) + sizeof(otIp6Address) + sizeof(uint8_t) + aCount;
    PBYTE Buffer = (PBYTE)malloc(BufferSize);
    if (Buffer == nullptr) return kThreadError_NoBufs;

    memcpy_s(Buffer, BufferSize, &aInstance->InterfaceGuid, sizeof(GUID));
    memcpy_s(Buffer + sizeof(GUID), BufferSize - sizeof(GUID), aDestination, sizeof(otIp6Address));
    memcpy_s(Buffer + sizeof(GUID) + sizeof(otIp6Address), BufferSize - sizeof(GUID) - sizeof(otIp6Address), &aCount, sizeof(aCount));
    memcpy_s(Buffer + sizeof(GUID) + sizeof(otIp6Address) + sizeof(uint8_t), BufferSize - sizeof(GUID) - sizeof(otIp6Address) - sizeof(uint8_t), aTlvTypes, aCount);
    
    ThreadError result = 
        DwordToThreadError(SendIOCTL(aInstance->ApiHandle, IOCTL_OTLWF_OT_SEND_DIAGNOSTIC_RESET, Buffer, BufferSize, nullptr, 0));

    free(Buffer);
    return result;
}

OTAPI 
ThreadError 
OTCALL
otCommissionerStart(
     _In_ otInstance *aInstance
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(SetIOCTL(aInstance, IOCTL_OTLWF_OT_COMMISIONER_START));
}

OTAPI 
ThreadError 
OTCALL
otCommissionerAddJoiner(
     _In_ otInstance *aInstance, 
    const otExtAddress *aExtAddress, 
    const char *aPSKd
    )
{
    if (aInstance == nullptr || aPSKd == nullptr) return kThreadError_InvalidArgs;

    size_t aPSKdLength = strnlen(aPSKd, OPENTHREAD_PSK_MAX_LENGTH + 1);
    if (aPSKdLength > OPENTHREAD_PSK_MAX_LENGTH)
    {
        return kThreadError_InvalidArgs;
    }

    uint8_t aExtAddressValid = aExtAddress ? 1 : 0;
    
    const ULONG BufferLength = sizeof(GUID) + sizeof(uint8_t) + sizeof(otExtAddress) + (ULONG)aPSKdLength + 1;
    BYTE Buffer[sizeof(GUID) + sizeof(uint8_t) + sizeof(otExtAddress) + OPENTHREAD_PSK_MAX_LENGTH + 1] = {0};
    memcpy_s(Buffer, sizeof(Buffer), &aInstance->InterfaceGuid, sizeof(GUID));
    memcpy_s(Buffer + sizeof(GUID), sizeof(Buffer) - sizeof(GUID), &aExtAddressValid, sizeof(aExtAddressValid));
    if (aExtAddressValid)
        memcpy_s(Buffer + sizeof(GUID) + sizeof(uint8_t), sizeof(Buffer) - sizeof(GUID) - sizeof(uint8_t), aExtAddress, sizeof(otExtAddress));
    memcpy_s(Buffer + sizeof(GUID) + sizeof(uint8_t) + sizeof(otExtAddress), sizeof(Buffer) - sizeof(GUID) - sizeof(uint8_t) - sizeof(otExtAddress), aPSKd, aPSKdLength);
    
    return DwordToThreadError(SendIOCTL(aInstance->ApiHandle, IOCTL_OTLWF_OT_COMMISIONER_ADD_JOINER, Buffer, BufferLength, nullptr, 0));
}

OTAPI 
ThreadError 
OTCALL
otCommissionerRemoveJoiner(
     _In_ otInstance *aInstance, 
    const otExtAddress *aExtAddress
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;

    uint8_t aExtAddressValid = aExtAddress ? 1 : 0;
    
    BYTE Buffer[sizeof(GUID) + sizeof(uint8_t) + sizeof(otExtAddress)] = {0};
    memcpy_s(Buffer, sizeof(Buffer), &aInstance->InterfaceGuid, sizeof(GUID));
    memcpy_s(Buffer + sizeof(GUID), sizeof(Buffer) - sizeof(GUID), &aExtAddressValid, sizeof(aExtAddressValid));
    if (aExtAddressValid)
        memcpy_s(Buffer + sizeof(GUID) + sizeof(uint8_t), sizeof(Buffer) - sizeof(GUID) - sizeof(uint8_t), aExtAddress, sizeof(otExtAddress));

    return DwordToThreadError(SendIOCTL(aInstance->ApiHandle, IOCTL_OTLWF_OT_COMMISIONER_REMOVE_JOINER, Buffer, sizeof(Buffer), nullptr, 0));
}

OTAPI 
ThreadError 
OTCALL
otCommissionerSetProvisioningUrl(
     _In_ otInstance *aInstance,
    const char *aProvisioningUrl
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;

    size_t aProvisioningUrlLength = aProvisioningUrl ? strnlen(aProvisioningUrl, OPENTHREAD_PROV_URL_MAX_LENGTH + 1) : 0;
    if (aProvisioningUrlLength > OPENTHREAD_PROV_URL_MAX_LENGTH)
    {
        return kThreadError_InvalidArgs;
    }
    
    const ULONG BufferLength = sizeof(GUID) + (ULONG)aProvisioningUrlLength + 1;
    BYTE Buffer[sizeof(GUID) + OPENTHREAD_PROV_URL_MAX_LENGTH + 1] = {0};
    memcpy_s(Buffer, sizeof(Buffer), &aInstance->InterfaceGuid, sizeof(GUID));
    if (aProvisioningUrlLength > 0)
        memcpy_s(Buffer + sizeof(GUID), sizeof(Buffer) - sizeof(GUID), aProvisioningUrl, aProvisioningUrlLength);
    
    return DwordToThreadError(SendIOCTL(aInstance->ApiHandle, IOCTL_OTLWF_OT_COMMISIONER_PROVISIONING_URL, Buffer, BufferLength, nullptr, 0));
}

OTAPI
ThreadError
OTCALL
otCommissionerAnnounceBegin(
    otInstance *aInstance,
    uint32_t aChannelMask,
    uint8_t aCount,
    uint16_t aPeriod,
    const otIp6Address *aAddress
    )
{
    if (aInstance == nullptr || aAddress == nullptr) return kThreadError_InvalidArgs;

    PackedBuffer5<GUID,uint32_t,uint8_t,uint16_t,otIp6Address> Buffer(aInstance->InterfaceGuid, aChannelMask, aCount, aPeriod, *aAddress);
    return DwordToThreadError(SendIOCTL(aInstance->ApiHandle, IOCTL_OTLWF_OT_COMMISIONER_ANNOUNCE_BEGIN, &Buffer, sizeof(Buffer), nullptr, 0));
}

OTAPI 
ThreadError 
OTCALL
otCommissionerStop(
     _In_ otInstance *aInstance
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(SetIOCTL(aInstance, IOCTL_OTLWF_OT_COMMISIONER_STOP));
}

OTAPI
ThreadError 
OTCALL
otCommissionerEnergyScan(
     _In_ otInstance *aInstance, 
    uint32_t aChannelMask, 
    uint8_t aCount, 
    uint16_t aPeriod,
    uint16_t aScanDuration, 
    const otIp6Address *aAddress,
    _In_ otCommissionerEnergyReportCallback aCallback, 
    _In_ void *aContext
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;

    aInstance->ApiHandle->SetCallback(
        aInstance->ApiHandle->CommissionerEnergyReportCallbacks,
        aInstance->InterfaceGuid, aCallback, aContext
        );

    PackedBuffer6<GUID,uint32_t,uint8_t,uint16_t,uint16_t,otIp6Address> Buffer(aInstance->InterfaceGuid, aChannelMask, aCount, aPeriod, aScanDuration, *aAddress);
    return DwordToThreadError(SendIOCTL(aInstance->ApiHandle, IOCTL_OTLWF_OT_COMMISSIONER_ENERGY_SCAN, &Buffer, sizeof(Buffer), nullptr, 0));
}

OTAPI
ThreadError 
OTCALL
otCommissionerPanIdQuery(
     _In_ otInstance *aInstance, 
    uint16_t aPanId, 
    uint32_t aChannelMask,
    const otIp6Address *aAddress,
     _In_ otCommissionerPanIdConflictCallback aCallback, 
     _In_ void *aContext
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;

    aInstance->ApiHandle->SetCallback(
        aInstance->ApiHandle->CommissionerPanIdConflictCallbacks,
        aInstance->InterfaceGuid, aCallback, aContext
        );

    PackedBuffer4<GUID,uint16_t,uint32_t,otIp6Address> Buffer(aInstance->InterfaceGuid, aPanId, aChannelMask, *aAddress);
    return DwordToThreadError(SendIOCTL(aInstance->ApiHandle, IOCTL_OTLWF_OT_COMMISSIONER_PANID_QUERY, &Buffer, sizeof(Buffer), nullptr, 0));
}

OTAPI 
ThreadError 
OTCALL 
otSendMgmtCommissionerGet(
     _In_ otInstance *aInstance, 
    const uint8_t *aTlvs, 
    uint8_t aLength
)
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    if (aTlvs == nullptr && aLength != 0) return kThreadError_InvalidArgs;
    
    DWORD BufferSize = sizeof(GUID) + sizeof(uint8_t) + aLength;
    PBYTE Buffer = (PBYTE)malloc(BufferSize);
    if (Buffer == nullptr) return kThreadError_NoBufs;

    memcpy_s(Buffer, BufferSize, &aInstance->InterfaceGuid, sizeof(GUID));
    memcpy_s(Buffer + sizeof(GUID), BufferSize - sizeof(GUID), &aLength, sizeof(aLength));
    if (aLength > 0)
        memcpy_s(Buffer + sizeof(GUID) + sizeof(uint8_t), BufferSize - sizeof(GUID) - sizeof(uint8_t), aTlvs, aLength);
    
    ThreadError result = 
        DwordToThreadError(SendIOCTL(aInstance->ApiHandle, IOCTL_OTLWF_OT_SEND_MGMT_COMMISSIONER_GET, Buffer, BufferSize, nullptr, 0));

    free(Buffer);
    return result;
}

OTAPI 
ThreadError 
OTCALL 
otSendMgmtCommissionerSet(
     _In_ otInstance *aInstance,
    const otCommissioningDataset *aDataset,
    const uint8_t *aTlvs,
    uint8_t aLength
    )
{
    if (aInstance == nullptr || aDataset == nullptr) return kThreadError_InvalidArgs;
    if (aTlvs == nullptr && aLength != 0) return kThreadError_InvalidArgs;
    
    DWORD BufferSize = sizeof(GUID) + sizeof(otCommissioningDataset) + sizeof(uint8_t) + aLength;
    PBYTE Buffer = (PBYTE)malloc(BufferSize);
    if (Buffer == nullptr) return kThreadError_NoBufs;

    memcpy_s(Buffer, BufferSize, &aInstance->InterfaceGuid, sizeof(GUID));
    memcpy_s(Buffer + sizeof(GUID), BufferSize - sizeof(GUID), aDataset, sizeof(otCommissioningDataset));
    memcpy_s(Buffer + sizeof(GUID) + sizeof(otCommissioningDataset), BufferSize - sizeof(GUID) - sizeof(otCommissioningDataset), &aLength, sizeof(aLength));
    if (aLength > 0)
        memcpy_s(Buffer + sizeof(GUID) + sizeof(otCommissioningDataset) + sizeof(uint8_t), BufferSize - sizeof(GUID) - sizeof(otCommissioningDataset) - sizeof(uint8_t), aTlvs, aLength);
    
    ThreadError result = 
        DwordToThreadError(SendIOCTL(aInstance->ApiHandle, IOCTL_OTLWF_OT_SEND_MGMT_COMMISSIONER_SET, Buffer, BufferSize, nullptr, 0));

    free(Buffer);
    return result;
}

OTAPI 
ThreadError 
OTCALL
otJoinerStart(
     _In_ otInstance *aInstance,
    const char *aPSKd, 
    const char *aProvisioningUrl,
    _In_ otJoinerCallback aCallback,
    _In_ void *aCallbackContext
    )
{
    if (aInstance == nullptr || aPSKd == nullptr) return kThreadError_InvalidArgs;

    otCommissionConfig config = {0};

    size_t aPSKdLength = strlen(aPSKd);
    size_t aProvisioningUrlLength = aProvisioningUrl == nullptr ? 0 : strlen(aProvisioningUrl);

    if (aPSKdLength > OPENTHREAD_PSK_MAX_LENGTH ||
        aProvisioningUrlLength > OPENTHREAD_PROV_URL_MAX_LENGTH)
    {
        return kThreadError_InvalidArgs;
    }

    memcpy_s(config.PSKd, sizeof(config.PSKd), aPSKd, aPSKdLength);
    memcpy_s(config.ProvisioningUrl, sizeof(config.ProvisioningUrl), aProvisioningUrl, aProvisioningUrlLength);

    return DwordToThreadError(SetIOCTL(aInstance, IOCTL_OTLWF_OT_JOINER_START, (const otCommissionConfig*)&config));
}

OTAPI 
ThreadError 
OTCALL
otJoinerStop(
     _In_ otInstance *aInstance
    )
{
    if (aInstance == nullptr) return kThreadError_InvalidArgs;
    return DwordToThreadError(SetIOCTL(aInstance, IOCTL_OTLWF_OT_JOINER_STOP));
}
