/*
 * This file contains NDIS5.X implementation of
 * OID-related adapter driver procedures
 *
 * Copyright (c) 2008-2017 Red Hat, Inc.
 *
 * 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 names of the copyright holders nor the names of their 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 HOLDERS 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 "ParaNdis5.h"
#include "ParaNdis-Oid.h"

#ifdef WPP_EVENT_TRACING
#include "ParaNdis5-Oid.tmh"
#endif

#define OIDENTRY(oid, el, xfl, xokl, flags) \
{ oid, el, xfl, xokl, flags, NULL }
#define OIDENTRYPROC(oid, el, xfl, xokl, flags, setproc) \
{ oid, el, xfl, xokl, flags, setproc }

static NDIS_TASK_OFFLOAD_HEADER ReservedHeader =
{
    NDIS_TASK_OFFLOAD_VERSION,
    sizeof(NDIS_TASK_OFFLOAD_HEADER),
    0,
    0,
    { IEEE_802_3_Encapsulation, { 1, 0 }, 0 }
};


static NDIS_OID SupportedOids[] = {
    OID_GEN_SUPPORTED_LIST,
    OID_GEN_HARDWARE_STATUS,
    OID_GEN_MEDIA_SUPPORTED,
    OID_GEN_MEDIA_IN_USE,
    OID_GEN_MAXIMUM_LOOKAHEAD,
    OID_GEN_MAXIMUM_FRAME_SIZE,
    OID_GEN_LINK_SPEED,
    OID_GEN_TRANSMIT_BUFFER_SPACE,
    OID_GEN_RECEIVE_BUFFER_SPACE,
    OID_GEN_TRANSMIT_BLOCK_SIZE,
    OID_GEN_RECEIVE_BLOCK_SIZE,
    OID_GEN_VENDOR_ID,
    OID_GEN_VENDOR_DESCRIPTION,
    OID_GEN_VENDOR_DRIVER_VERSION,
    OID_GEN_CURRENT_PACKET_FILTER,
    OID_GEN_CURRENT_LOOKAHEAD,
    OID_GEN_DRIVER_VERSION,
    OID_GEN_MAXIMUM_TOTAL_SIZE,
    OID_GEN_PROTOCOL_OPTIONS,
    OID_GEN_MAC_OPTIONS,
    OID_GEN_MEDIA_CONNECT_STATUS,
    OID_GEN_MAXIMUM_SEND_PACKETS,
    OID_GEN_XMIT_OK,
    OID_GEN_RCV_OK,
    OID_GEN_VLAN_ID,
    OID_GEN_XMIT_ERROR,
    OID_GEN_RCV_ERROR,
    OID_GEN_RCV_NO_BUFFER,
    OID_GEN_RCV_CRC_ERROR,
    OID_GEN_TRANSMIT_QUEUE_LENGTH,
    OID_802_3_PERMANENT_ADDRESS,
    OID_802_3_CURRENT_ADDRESS,
    OID_802_3_MULTICAST_LIST,
    OID_802_3_MAC_OPTIONS,
    OID_802_3_MAXIMUM_LIST_SIZE,
    OID_802_3_RCV_ERROR_ALIGNMENT,
    OID_802_3_XMIT_ONE_COLLISION,
    OID_802_3_XMIT_MORE_COLLISIONS,
    OID_802_3_XMIT_DEFERRED,
    OID_802_3_XMIT_MAX_COLLISIONS,
    OID_802_3_RCV_OVERRUN,
    OID_802_3_XMIT_UNDERRUN,
    OID_802_3_XMIT_HEARTBEAT_FAILURE,
    OID_802_3_XMIT_TIMES_CRS_LOST,
    OID_802_3_XMIT_LATE_COLLISIONS,
    OID_PNP_CAPABILITIES,
    OID_PNP_SET_POWER,
    OID_PNP_QUERY_POWER,
    OID_PNP_ADD_WAKE_UP_PATTERN,
    OID_PNP_REMOVE_WAKE_UP_PATTERN,
    OID_PNP_ENABLE_WAKE_UP,
    OID_TCP_TASK_OFFLOAD
};

static NDIS_STATUS OnOidSetNdis5Offload(PARANDIS_ADAPTER *pContext, tOidDesc *pOid);
static NDIS_STATUS CreateOffloadInfo5ForQuery(PARANDIS_ADAPTER *pContext, tOidDesc *pOid, PVOID *ppInfo, PULONG pulSize);
static NDIS_STATUS CreateOffloadInfo5Internal(PARANDIS_ADAPTER *pContext, PVOID *ppInfo, PULONG pulSize, PCCHAR reason, NDIS_TASK_OFFLOAD_HEADER *pHeader);

/**********************************************************
Structure defining how to process all the oids
***********************************************************/
// oid                                          e f ok flags      set procedure
static const tOidWhatToDo OidsDB[] =
{
OIDENTRY(OID_GEN_SUPPORTED_LIST,                2,2,4, ohfQueryStat     ),
OIDENTRY(OID_GEN_HARDWARE_STATUS,               2,0,4, ohfQuery         ),
OIDENTRY(OID_GEN_MEDIA_SUPPORTED,               2,0,4, ohfQuery         ),
OIDENTRY(OID_GEN_MEDIA_IN_USE,                  2,0,4, ohfQuery         ),
OIDENTRY(OID_GEN_MAXIMUM_LOOKAHEAD,             2,0,4, ohfQuery         ),
OIDENTRY(OID_GEN_MAXIMUM_FRAME_SIZE,            2,0,4, ohfQuery         ),
OIDENTRY(OID_GEN_LINK_SPEED,                    6,0,6, ohfQuery         ),
OIDENTRY(OID_GEN_TRANSMIT_BUFFER_SPACE,         2,0,4, ohfQuery         ),
OIDENTRY(OID_GEN_RECEIVE_BUFFER_SPACE,          2,0,4, ohfQuery         ),
OIDENTRY(OID_GEN_TRANSMIT_BLOCK_SIZE,           2,0,4, ohfQuery         ),
OIDENTRY(OID_GEN_RECEIVE_BLOCK_SIZE,            2,0,4, ohfQuery         ),
OIDENTRY(OID_GEN_VENDOR_ID,                     2,0,4, ohfQueryStat     ),
OIDENTRY(OID_GEN_VENDOR_DESCRIPTION,            2,2,4, ohfQuery         ),
OIDENTRYPROC(OID_GEN_CURRENT_PACKET_FILTER,     2,0,4, ohfQuerySet, ParaNdis_OnSetPacketFilter),
OIDENTRYPROC(OID_GEN_CURRENT_LOOKAHEAD,         2,0,4, ohfQuerySet, ParaNdis_OnSetLookahead),
OIDENTRY(OID_GEN_DRIVER_VERSION,                2,0,4, ohfQuery         ),
OIDENTRY(OID_GEN_MAXIMUM_TOTAL_SIZE,            2,0,4, ohfQuery         ),
OIDENTRY(OID_GEN_PROTOCOL_OPTIONS,              2,0,4, 0                ),
OIDENTRY(OID_GEN_MAC_OPTIONS,                   2,0,4, ohfQuery         ),
OIDENTRY(OID_GEN_MEDIA_CONNECT_STATUS,          6,0,6, ohfQuery         ),
OIDENTRY(OID_GEN_MAXIMUM_SEND_PACKETS,          2,0,4, ohfQuery         ),
OIDENTRY(OID_GEN_VENDOR_DRIVER_VERSION,         2,0,4, ohfQuery         ),
OIDENTRY(OID_GEN_SUPPORTED_GUIDS,               2,2,4, 0                ),
OIDENTRY(OID_GEN_TRANSPORT_HEADER_OFFSET,       2,4,4, 0                ),
OIDENTRY(OID_GEN_MEDIA_CAPABILITIES,            2,4,4, 0                ),
OIDENTRY(OID_GEN_PHYSICAL_MEDIUM,               2,4,4, 0                ),
OIDENTRY(OID_GEN_XMIT_OK,                       6,0,6, ohfQuery3264     ),
OIDENTRY(OID_GEN_RCV_OK,                        6,0,4, ohfQuery3264     ),
OIDENTRY(OID_GEN_XMIT_ERROR,                    6,0,6, ohfQuery3264     ),
OIDENTRY(OID_GEN_RCV_ERROR,                     6,0,6, ohfQuery3264     ),
OIDENTRY(OID_GEN_RCV_NO_BUFFER,                 2,0,4, ohfQuery3264     ),
OIDENTRY(OID_GEN_DIRECTED_BYTES_XMIT,           2,4,4, 0                ),
OIDENTRY(OID_GEN_DIRECTED_FRAMES_XMIT,          2,4,4, 0                ),
OIDENTRY(OID_GEN_MULTICAST_BYTES_XMIT,          2,4,4, 0                ),
OIDENTRY(OID_GEN_MULTICAST_FRAMES_XMIT,         2,4,4, 0                ),
OIDENTRY(OID_GEN_BROADCAST_BYTES_XMIT,          2,4,4, 0                ),
OIDENTRY(OID_GEN_BROADCAST_FRAMES_XMIT,         2,4,4, 0                ),
OIDENTRY(OID_GEN_DIRECTED_BYTES_RCV,            2,4,4, 0                ),
OIDENTRY(OID_GEN_DIRECTED_FRAMES_RCV,           2,4,4, 0                ),
OIDENTRY(OID_GEN_MULTICAST_BYTES_RCV,           2,4,4, 0                ),
OIDENTRY(OID_GEN_MULTICAST_FRAMES_RCV,          2,4,4, 0                ),
OIDENTRY(OID_GEN_BROADCAST_BYTES_RCV,           2,4,4, 0                ),
OIDENTRY(OID_GEN_BROADCAST_FRAMES_RCV,          2,4,4, 0                ),
OIDENTRY(OID_GEN_RCV_CRC_ERROR,                 2,0,4, ohfQuery3264     ),
OIDENTRY(OID_GEN_TRANSMIT_QUEUE_LENGTH,         2,0,4, ohfQuery         ),
OIDENTRY(OID_GEN_GET_TIME_CAPS,                 2,4,4, 0                ),
OIDENTRY(OID_GEN_GET_NETCARD_TIME,              2,4,4, 0                ),
OIDENTRY(OID_GEN_NETCARD_LOAD,                  2,4,4, 0                ),
OIDENTRY(OID_GEN_DEVICE_PROFILE,                2,4,4, 0                ),
OIDENTRY(OID_GEN_INIT_TIME_MS,                  2,4,4, 0                ),
OIDENTRY(OID_GEN_RESET_COUNTS,                  2,4,4, 0                ),
OIDENTRY(OID_GEN_MEDIA_SENSE_COUNTS,            2,4,4, 0                ),
OIDENTRY(OID_PNP_CAPABILITIES,                  2,0,4, ohfQuery         ),
OIDENTRY(OID_PNP_QUERY_POWER,                   2,0,4, ohfQuery         ),
OIDENTRY(OID_802_3_PERMANENT_ADDRESS,           2,0,4, ohfQueryStat     ),
OIDENTRY(OID_802_3_CURRENT_ADDRESS,             2,0,4, ohfQueryStat     ),
OIDENTRY(OID_802_3_MAXIMUM_LIST_SIZE,           2,0,4, ohfQuery         ),
OIDENTRY(OID_802_3_MAC_OPTIONS,                 2,4,4, ohfQuery         ),
OIDENTRY(OID_802_3_RCV_ERROR_ALIGNMENT,         2,0,4, ohfQuery3264     ),
OIDENTRY(OID_802_3_XMIT_ONE_COLLISION,          2,4,4, ohfQuery3264     ),
OIDENTRY(OID_802_3_XMIT_MORE_COLLISIONS,        2,4,4, ohfQuery3264     ),
OIDENTRY(OID_802_3_XMIT_DEFERRED,               2,0,4, ohfQuery3264     ),
OIDENTRY(OID_802_3_XMIT_MAX_COLLISIONS,         2,0,4, ohfQuery3264     ),
OIDENTRY(OID_802_3_RCV_OVERRUN,                 2,0,4, ohfQuery3264     ),
OIDENTRY(OID_802_3_XMIT_UNDERRUN,               2,0,4, ohfQuery3264     ),
OIDENTRY(OID_802_3_XMIT_HEARTBEAT_FAILURE,      2,0,4, ohfQuery3264     ),
OIDENTRY(OID_802_3_XMIT_TIMES_CRS_LOST,         2,0,4, ohfQuery3264     ),
OIDENTRY(OID_802_3_XMIT_LATE_COLLISIONS,        2,0,4, ohfQuery3264     ),
OIDENTRY(OID_GEN_MACHINE_NAME,                  2,4,4, 0                ),
OIDENTRY(OID_IP4_OFFLOAD_STATS,                 4,4,4, 0                ),
OIDENTRY(OID_IP6_OFFLOAD_STATS,                 4,4,4, 0                ),
OIDENTRY(OID_802_11_CAPABILITY,                 4,4,4, 0                ),
OIDENTRYPROC(OID_PNP_ADD_WAKE_UP_PATTERN,       2,0,4, ohfSet,          ParaNdis_OnAddWakeupPattern),
OIDENTRYPROC(OID_PNP_REMOVE_WAKE_UP_PATTERN,    2,0,4, ohfSet,          ParaNdis_OnRemoveWakeupPattern),
OIDENTRYPROC(OID_PNP_ENABLE_WAKE_UP,            2,0,4, ohfQuerySet,     ParaNdis_OnEnableWakeup),
OIDENTRYPROC(OID_PNP_SET_POWER,                 2,0,4, ohfSet | ohfSetMoreOK,   ParaNdis_OnSetPower),
OIDENTRYPROC(OID_GEN_CURRENT_LOOKAHEAD,         2,0,4, ohfQuerySet,     ParaNdis_OnSetLookahead),
OIDENTRYPROC(OID_GEN_CURRENT_PACKET_FILTER,     2,0,4, ohfQuerySet,     ParaNdis_OnSetPacketFilter),
OIDENTRYPROC(OID_802_3_MULTICAST_LIST,          2,0,4, ohfQuerySet,     ParaNdis_OnOidSetMulticastList),
OIDENTRY(OID_FFP_SUPPORT,                       2,4,4, 0                ),
OIDENTRYPROC(OID_TCP_TASK_OFFLOAD,              0,0,0, ohfQuerySet, OnOidSetNdis5Offload),
OIDENTRYPROC(OID_GEN_VLAN_ID,                   0,4,4, ohfQuerySet, ParaNdis_OnSetVlanId),
OIDENTRY(0x00010203 /*(OID_GEN_RECEIVE_SCALE_CAPABILITIES)*/, 2,4,4, 0  ),
OIDENTRY(0x0001021F /*(OID_GEN_RECEIVE_HASH)*/, 2,4,4, 0                ),
OIDENTRY(0,                                     4,4,4, 0),
};

/**********************************************************
Returns to common query processor the array of supported oids
***********************************************************/
void ParaNdis_GetSupportedOid(PVOID *pOidsArray, PULONG pLength)
{
    *pOidsArray     = SupportedOids;
    *pLength        = sizeof(SupportedOids);
}


/*****************************************************************
Handles NDIS5 specific OID, all the rest handled by common handler
*****************************************************************/
static NDIS_STATUS ParaNdis_OidQuery(PARANDIS_ADAPTER *pContext, tOidDesc *pOid)
{
    NDIS_STATUS status;
    BOOLEAN bFreeInfo = FALSE;
    PVOID pInfo = NULL;
    ULONG ulSize = 0;
    ULONG ulLinkSpeed = 0;

    switch(pOid->Oid)
    {
        case OID_TCP_TASK_OFFLOAD:
            status = CreateOffloadInfo5ForQuery(pContext, pOid, &pInfo, &ulSize);
            bFreeInfo = pInfo != NULL;
            break;
        case OID_GEN_LINK_SPEED:
            {
                /* units are 100 bps */
                ulLinkSpeed = (ULONG)(PARANDIS_FORMAL_LINK_SPEED / 100);
                pInfo = &ulLinkSpeed;
                ulSize = sizeof(ulLinkSpeed);
                status = NDIS_STATUS_SUCCESS;
            }
            break;
        default:
            return ParaNdis_OidQueryCommon(pContext, pOid);
    }
    if (status == NDIS_STATUS_SUCCESS)
    {
        status = ParaNdis_OidQueryCopy(pOid, pInfo, ulSize, bFreeInfo);
    }
    else if (bFreeInfo)
    {
        NdisFreeMemory(pInfo, 0, 0);
    }
    return status;
}

/**********************************************************
NDIS required procedure of OID QUERY
Just passes all the supported oids to common query procedure
Return value:
    NDIS_STATUS                 as returned from common code
    NDIS_STATUS_NOT_SUPPORTED   if suppressed in the table
***********************************************************/
NDIS_STATUS ParaNdis5_QueryOID(IN NDIS_HANDLE MiniportAdapterContext,
                                    IN NDIS_OID Oid,
                                    IN PVOID InformationBuffer,
                                    IN ULONG InformationBufferLength,
                                    OUT PULONG BytesWritten,
                                    OUT PULONG BytesNeeded)
{
    NDIS_STATUS  status = NDIS_STATUS_NOT_SUPPORTED;
    tOidWhatToDo Rules;
    PARANDIS_ADAPTER *pContext = (PARANDIS_ADAPTER *)MiniportAdapterContext;
    tOidDesc _oid;
    ParaNdis_GetOidSupportRules(Oid, &Rules, OidsDB);
    _oid.ulToDoFlags = Rules.Flags;
    *BytesWritten = 0;
    *BytesNeeded  = 0;
    ParaNdis_DebugHistory(pContext, hopOidRequest, NULL, Oid, 0, 1);
    DPrintf(Rules.nEntryLevel, ("[%s], id 0x%X(%s) of %d", __FUNCTION__,
                Oid,
                Rules.name,
                InformationBufferLength));
    _oid.Oid = Oid;
    _oid.InformationBuffer = InformationBuffer;
    _oid.InformationBufferLength = InformationBufferLength;
    _oid.pBytesNeeded = BytesNeeded;
    _oid.pBytesRead   = BytesWritten;
    _oid.pBytesWritten = BytesWritten;
    if (pContext->bSurprizeRemoved) status = NDIS_STATUS_NOT_ACCEPTED;
    else if (Rules.Flags & ohfQuery) status = ParaNdis_OidQuery(pContext, &_oid);


    ParaNdis_DebugHistory(pContext, hopOidRequest, NULL, Oid, status, 0);
    DPrintf((status != NDIS_STATUS_SUCCESS) ? Rules.nExitFailLevel : Rules.nExitOKLevel,
        ("[%s] , id 0x%X(%s) (%X), written %d, needed %d",
        __FUNCTION__,
        Rules.oid,
        Rules.name,
        status,
        *BytesWritten,
        *BytesNeeded));
    return status;

}

/**********************************************************
NDIS required procedure of OID SET
Just passes all the supported oids to common set procedure
Return value:
    NDIS_STATUS                 as returned from set procedure
    NDIS_STATUS_NOT_SUPPORTED   if support not defined in the table
***********************************************************/
NDIS_STATUS ParaNdis5_SetOID(IN NDIS_HANDLE MiniportAdapterContext,
                                  IN NDIS_OID Oid,
                                  IN PVOID InformationBuffer,
                                  IN ULONG InformationBufferLength,
                                  OUT PULONG BytesRead,
                                  OUT PULONG BytesNeeded)
{
    NDIS_STATUS  status = NDIS_STATUS_NOT_SUPPORTED;
    tOidWhatToDo Rules;
    PARANDIS_ADAPTER *pContext = (PARANDIS_ADAPTER *)MiniportAdapterContext;
    tOidDesc _oid;
    ParaNdis_GetOidSupportRules(Oid, &Rules, OidsDB);
    _oid.ulToDoFlags = Rules.Flags;
    *BytesRead = 0;
    *BytesNeeded = 0;
    ParaNdis_DebugHistory(pContext, hopOidRequest, NULL, Oid, 1, 1);
    DPrintf(Rules.nEntryLevel, ("[%s], id 0x%X(%s) of %d", __FUNCTION__,
                Oid,
                Rules.name,
                InformationBufferLength));
    _oid.Oid = Oid;
    _oid.InformationBuffer = InformationBuffer;
    _oid.InformationBufferLength = InformationBufferLength;
    _oid.pBytesNeeded = BytesNeeded;
    _oid.pBytesRead   = BytesRead;
    _oid.pBytesWritten = BytesRead;
    if (pContext->bSurprizeRemoved) status = NDIS_STATUS_NOT_ACCEPTED;
    else if (Rules.Flags & ohfSet)
    {
        if (Rules.OidSetProc) status = Rules.OidSetProc(pContext, &_oid);
        else
        {
            DPrintf(0, ("[%s] ERROR in OID redirection table", __FUNCTION__));
            status = NDIS_STATUS_INVALID_OID;
        }
    }
    ParaNdis_DebugHistory(pContext, hopOidRequest, NULL, Oid, status, 0);
    if  (status != NDIS_STATUS_PENDING)
    {
        DPrintf((status != NDIS_STATUS_SUCCESS) ? Rules.nExitFailLevel : Rules.nExitOKLevel,
            ("[%s] , id 0x%X(%s) (%X), read %d, needed %d", __FUNCTION__,
            Rules.oid, Rules.name, status, *BytesRead, *BytesNeeded));
    }
    return status;
}

static void OnSetPowerWorkItem(NDIS_WORK_ITEM * pWorkItem, PVOID  Context)
{
    NDIS_STATUS status = NDIS_STATUS_SUCCESS;
    tPowerWorkItem *pwi = (tPowerWorkItem *)pWorkItem;
    PARANDIS_ADAPTER *pContext = pwi->pContext;
    if (pwi->state == NetDeviceStateD0)
    {
        status = ParaNdis_PowerOn(pContext);
    }
    else
    {
        ParaNdis_PowerOff(pContext);
    }
    NdisFreeMemory(pwi, 0, 0);
    ParaNdis_DebugHistory(pContext, hopOidRequest, NULL, OID_PNP_SET_POWER, 0, 2);
    NdisMSetInformationComplete(pContext->MiniportHandle, status);
}

/**********************************************************
NDIS5.X handler of power management
***********************************************************/
NDIS_STATUS ParaNdis_OnSetPower(PARANDIS_ADAPTER *pContext, tOidDesc *pOid)
{
    NDIS_STATUS status;
    NDIS_DEVICE_POWER_STATE newState;
    DEBUG_ENTRY(0);
    status = ParaNdis_OidSetCopy(pOid, &newState, sizeof(newState));
    if (status == NDIS_STATUS_SUCCESS)
    {
        tPowerWorkItem *pwi = ParaNdis_AllocateMemory(pContext, sizeof(tPowerWorkItem));
        status = NDIS_STATUS_FAILURE;
        if (pwi)
        {
            pwi->pContext = pContext;
            pwi->state    = newState;
            NdisInitializeWorkItem(&pwi->wi, OnSetPowerWorkItem, pwi);
            if (NdisScheduleWorkItem(&pwi->wi) == NDIS_STATUS_SUCCESS)
            {
                status = NDIS_STATUS_PENDING;
            }
            else
                NdisFreeMemory(pwi, 0, 0);
        }
    }
    return status;
}

/***************************************************
check that the incoming NDIS_TASK_TCP_IP_CHECKSUM
does not enable options which we do not support
***************************************************/
static BOOLEAN IsValidPcs(  PARANDIS_ADAPTER *pContext, NDIS_TASK_TCP_IP_CHECKSUM *pcs)
{
    tOffloadSettingsFlags f;
    BOOLEAN bInvalid = FALSE;
    ParaNdis_ResetOffloadSettings(pContext, &f, NULL);
    bInvalid |= pcs->V4Receive.IpChecksum && !f.fRxIPChecksum;
    bInvalid |= pcs->V4Receive.IpOptionsSupported && !f.fRxIPOptions;
    bInvalid |= pcs->V4Receive.TcpChecksum && !f.fRxTCPChecksum;
    bInvalid |= pcs->V4Receive.TcpOptionsSupported && !f.fRxTCPOptions;
    bInvalid |= pcs->V4Receive.UdpChecksum && !f.fRxUDPChecksum;

    bInvalid |= pcs->V4Transmit.IpChecksum && !f.fTxIPChecksum;
    bInvalid |= pcs->V4Transmit.IpOptionsSupported && !f.fTxIPOptions;
    bInvalid |= pcs->V4Transmit.TcpChecksum && !f.fTxTCPChecksum;
    bInvalid |= pcs->V4Transmit.TcpOptionsSupported && !f.fTxTCPOptions;
    bInvalid |= pcs->V4Transmit.UdpChecksum && !f.fTxUDPChecksum;
    return !bInvalid;
}

/***************************************************
check that the incoming NDIS_TASK_TCP_LARGE_SEND
does not enable options which we do not support
***************************************************/
static BOOLEAN IsValidPls(  PARANDIS_ADAPTER *pContext, NDIS_TASK_TCP_LARGE_SEND *pls)
{
    tOffloadSettingsFlags f;
    BOOLEAN bInvalid = FALSE;
    ParaNdis_ResetOffloadSettings(pContext, &f, NULL);
    bInvalid |= pls->Version != NDIS_TASK_TCP_LARGE_SEND_V0;
    bInvalid |= pls->IpOptions && !f.fTxLsoIP;
    bInvalid |= pls->TcpOptions && !f.fTxLsoTCP;
    bInvalid |= (pls->IpOptions || pls->TcpOptions || pls->MaxOffLoadSize) && !f.fTxLso;
    bInvalid |= pls->MinSegmentCount < PARANDIS_MIN_LSO_SEGMENTS;
    return !bInvalid;
}

static NDIS_STATUS ParseOffloadTask(
    PARANDIS_ADAPTER *pContext,
    BOOLEAN bApply, /* for 'set'*/
    NDIS_TASK_OFFLOAD *pto,
    ULONG offset,
    ULONG maxSize)
{
    NDIS_STATUS status = NDIS_STATUS_SUCCESS;
    NDIS_TASK_TCP_IP_CHECKSUM *pcs = NULL;
    NDIS_TASK_TCP_LARGE_SEND  *pls = NULL;
    NDIS_TASK_IPSEC *pips = NULL;
    LPCSTR sName = NULL;
    ULONG TaskBufferSize = 0, tailOffset = 0;
    switch(pto->Task)
    {
    case TcpIpChecksumNdisTask:
        pcs = (NDIS_TASK_TCP_IP_CHECKSUM *)pto->TaskBuffer;
        TaskBufferSize = sizeof(*pcs);
        sName = "TcpIpChecksumNdisTask";
        break;
    case TcpLargeSendNdisTask:
        pls = (NDIS_TASK_TCP_LARGE_SEND *)pto->TaskBuffer;
        TaskBufferSize = sizeof(*pls);
        sName = "TcpLargeSendNdisTask";
        break;
    case IpSecNdisTask:
        pips = (NDIS_TASK_IPSEC *)pto->TaskBuffer;
        TaskBufferSize = sizeof(*pips);
        sName = "IpSecNdisTask";
        break;
    default:
        break;
    }
    tailOffset = offset + RtlPointerToOffset(pto, &pto->TaskBuffer) + TaskBufferSize;
    if (!TaskBufferSize)
    {
        DPrintf(0, ("[%s], unknown offload task %d", __FUNCTION__, pto->Task));
    }
    else if (tailOffset > maxSize)
    {
        DPrintf(0, ("[%s], can not parse %s at offset %d, tail at %d", __FUNCTION__, sName, offset, tailOffset));
        status = NDIS_STATUS_BUFFER_TOO_SHORT;
    }
    else if (TaskBufferSize > pto->TaskBufferLength)
    {
        DPrintf(0, ("[%s], invalid size of %s", __FUNCTION__, sName));
        status = NDIS_STATUS_BUFFER_TOO_SHORT;
    }
    else if (pcs)
    {
        DPrintf(0, ("[%s], parsing %s", __FUNCTION__, sName));
        DPrintf(0, ("Rx4: checksum IP(%d),TCP(%d),UDP(%d), options IP(%d),TCP(%d)",
            pcs->V4Receive.IpChecksum, pcs->V4Receive.TcpChecksum, pcs->V4Receive.UdpChecksum,
            pcs->V4Receive.IpOptionsSupported, pcs->V4Receive.TcpOptionsSupported
            ));
        DPrintf(0, ("Tx4: checksum IP(%d),TCP(%d),UDP(%d), options IP(%d),TCP(%d)",
            pcs->V4Transmit.IpChecksum, pcs->V4Transmit.TcpChecksum, pcs->V4Transmit.UdpChecksum,
            pcs->V4Transmit.IpOptionsSupported, pcs->V4Transmit.TcpOptionsSupported
            ));
        if (bApply)
        {
            if (IsValidPcs(pContext, pcs))
            {
                tOffloadSettingsFlags *pf = &pContext->Offload.flags;
                pf->fTxIPChecksum = !!pcs->V4Transmit.IpChecksum;
                pf->fTxTCPChecksum = !!pcs->V4Transmit.TcpChecksum;
                pf->fTxUDPChecksum = !!pcs->V4Transmit.UdpChecksum;
                pf->fTxTCPOptions = !!pcs->V4Transmit.TcpOptionsSupported;
                pf->fTxIPOptions = !!pcs->V4Transmit.IpOptionsSupported;
                pf->fRxIPChecksum = !!pcs->V4Receive.IpChecksum;
                pf->fRxIPOptions = !!pcs->V4Receive.IpOptionsSupported;
                pf->fRxTCPChecksum = !!pcs->V4Receive.TcpChecksum;
                pf->fRxTCPOptions = !!pcs->V4Receive.TcpOptionsSupported;
                pf->fRxUDPChecksum = !!pcs->V4Receive.UdpChecksum;
            }
            else
                status = STATUS_NOT_SUPPORTED;
        }
    }
    else if (pls)
    {
        DPrintf(0, ("[%s], parsing %s version %d", __FUNCTION__, sName, pls->Version));
        DPrintf(0, ("options IP(%d),TCP(%d),MaxOffload %d, MinSegments %d",
            pls->IpOptions, pls->TcpOptions, pls->MaxOffLoadSize, pls->MinSegmentCount));
        if (bApply)
        {
            if (IsValidPls(pContext, pls))
            {
                tOffloadSettingsFlags *pf = &pContext->Offload.flags;
                pf->fTxLsoIP = !!pls->IpOptions;
                pf->fTxLsoTCP = !!pls->TcpOptions;
                pf->fTxLso = 1;
            }
            else
                status = STATUS_NOT_SUPPORTED;
        }
    }
    else if (pips)
    {
        DPrintf(0, ("[%s], parsing %s", __FUNCTION__, sName));
    }
    return status;
}

static FORCEINLINE BOOLEAN ValidateOffloadHeader(NDIS_TASK_OFFLOAD_HEADER *pth)
{
    return
        pth->EncapsulationFormat.Encapsulation == IEEE_802_3_Encapsulation &&
        pth->Version == NDIS_TASK_OFFLOAD_VERSION &&
        pth->Size == sizeof(*pth);
}

static NDIS_STATUS ParseOffload(
    PARANDIS_ADAPTER *pContext,
    NDIS_TASK_OFFLOAD_HEADER *pth,
    ULONG size,
    BOOLEAN bApply,
    PCCHAR reason,
    BOOLEAN headerOnly)
{
    NDIS_STATUS status = NDIS_STATUS_NOT_SUPPORTED;
    BOOLEAN bReset = FALSE;
    ULONG ulNoCapabilities = 0;
    DPrintf(0, ("[%s](%s), format %d", __FUNCTION__, reason,
        pth->EncapsulationFormat.Encapsulation));
    if (ValidateOffloadHeader(pth))
    {
        PUCHAR p = (PUCHAR)pth;
        LONG  offset = (LONG)pth->OffsetFirstTask;
        status = NDIS_STATUS_SUCCESS;
        DPrintf(0, ("[%s], header version %d, ip header at %d, fixed %d, first at %d", __FUNCTION__,
            pth->Version,
            pth->EncapsulationFormat.EncapsulationHeaderSize,
            pth->EncapsulationFormat.Flags.FixedHeaderSize,
            offset));
        if (!offset && bApply)
        {
            /* disable all the capabilities */
            // according to DDK, 0 at first task offset means disabling all the capabilities
            DPrintf(0, ("[%s] RESETTING offload capabilities", __FUNCTION__));
            ParaNdis_ResetOffloadSettings(pContext, NULL, &ulNoCapabilities);
            bReset = TRUE;
        }
        while (!headerOnly && offset > 0 && (offset + sizeof(NDIS_TASK_OFFLOAD)) < size)
        {
            NDIS_TASK_OFFLOAD *pto = (NDIS_TASK_OFFLOAD *)(p + offset);
            if (pto->Version != NDIS_TASK_OFFLOAD_VERSION)
            {
                DPrintf(0, ("[%s], unexpected TO version %d at %d",
                    __FUNCTION__, pto->Version, offset));
                status = NDIS_STATUS_INVALID_DATA;
                break;
            }
            status = ParseOffloadTask(pContext, bApply, pto, offset, size);
            if (!pto->OffsetNextTask || status != NDIS_STATUS_SUCCESS)
                break;
            offset += pto->OffsetNextTask;
        }
    }
    if (status == STATUS_SUCCESS && bApply)
        pContext->Offload.ipHeaderOffset = bReset ? 0: pth->EncapsulationFormat.EncapsulationHeaderSize;
    return status;
}

/********************************************************
Fill offload query structure according to our capabilities
********************************************************/
static BOOLEAN GetTcpIpCheckSumCapabilities(
    PARANDIS_ADAPTER *pContext,
    NDIS_TASK_TCP_IP_CHECKSUM *pcs)
{
    tOffloadSettingsFlags f;
    NdisZeroMemory(pcs, sizeof(*pcs));
    ParaNdis_ResetOffloadSettings(pContext, &f, NULL);
    pcs->V4Transmit.IpChecksum = !!f.fTxIPChecksum;
    pcs->V4Transmit.TcpChecksum = !!f.fTxTCPChecksum;
    pcs->V4Transmit.UdpChecksum = !!f.fTxUDPChecksum;
    pcs->V4Transmit.IpOptionsSupported = !!f.fTxIPOptions;
    pcs->V4Transmit.TcpOptionsSupported = !!f.fTxTCPOptions;
    pcs->V4Receive.IpChecksum = !!f.fRxIPChecksum;
    pcs->V4Receive.IpOptionsSupported = !!f.fRxIPOptions;
    pcs->V4Receive.TcpChecksum = !!f.fRxTCPChecksum;
    pcs->V4Receive.TcpOptionsSupported = !!f.fRxTCPOptions;
    pcs->V4Receive.UdpChecksum = !!f.fRxUDPChecksum;

    return
        pcs->V4Transmit.IpChecksum ||
        pcs->V4Transmit.TcpChecksum ||
        pcs->V4Transmit.UdpChecksum ||
        pcs->V4Receive.IpChecksum ||
        pcs->V4Receive.TcpChecksum ||
        pcs->V4Receive.UdpChecksum;
}

/********************************************************
Fill offload query structure according to our capabilities
********************************************************/
static BOOLEAN GetLargeSendCapabilities(
    PARANDIS_ADAPTER *pContext,
    NDIS_TASK_TCP_LARGE_SEND  *pls)
{
    tOffloadSettingsFlags f;
    NdisZeroMemory(pls, sizeof(*pls));
    ParaNdis_ResetOffloadSettings(pContext, &f, NULL);
    pls->Version = NDIS_TASK_TCP_LARGE_SEND_V0;
    pls->IpOptions = !!f.fTxLsoIP;
    pls->TcpOptions = !!f.fTxLsoTCP;
    pls->MinSegmentCount = PARANDIS_MIN_LSO_SEGMENTS;
    pls->MaxOffLoadSize = pContext->Offload.maxPacketSize;
    return f.fTxLso != 0;
}

/********************************************************
Allocate and fill our capabilities, dependent on registry setting
Note than NDIS test of WLK1.2 and 1.3 fail (offloadmisc)
if CS capability indicated and passes if only LSO indicated
********************************************************/
NDIS_STATUS CreateOffloadInfo5Internal(
    PARANDIS_ADAPTER *pContext,
    PVOID *ppInfo,
    PULONG pulSize,
    PCCHAR reason,
    NDIS_TASK_OFFLOAD_HEADER *pHeader)
{
    NDIS_STATUS status = NDIS_STATUS_RESOURCES;
    ULONG size =
        sizeof(NDIS_TASK_OFFLOAD_HEADER) +
        sizeof(NDIS_TASK_OFFLOAD) + sizeof(NDIS_TASK_TCP_IP_CHECKSUM) +
        sizeof(NDIS_TASK_OFFLOAD) + sizeof(NDIS_TASK_TCP_LARGE_SEND);
    *ppInfo = ParaNdis_AllocateMemory(pContext, size);
    if (*ppInfo)
    {
        ULONG flags = 0;
        NDIS_TASK_TCP_IP_CHECKSUM cs;
        NDIS_TASK_TCP_LARGE_SEND lso;
        flags |= GetTcpIpCheckSumCapabilities(pContext, &cs) ? 2 : 0;
        flags |= GetLargeSendCapabilities(pContext, &lso) ? 1 : 0;
        if (flags)
        {
            NDIS_TASK_OFFLOAD_HEADER *ph;
            NDIS_TASK_OFFLOAD *pto;
            UINT i = 0;
            ULONG *pOffset;
            PVOID base;
            *pulSize = size;
            NdisZeroMemory(*ppInfo, size);
            ph = (NDIS_TASK_OFFLOAD_HEADER *)*ppInfo;
            *ph = *pHeader;
            pto = (NDIS_TASK_OFFLOAD *)(ph + 1);
            base = ph;
            pOffset = &ph->OffsetFirstTask;
            ph->OffsetFirstTask = 0;
            do
            {
                if (flags & (1 << i))
                {
                    flags &= ~(1 << i);
                    pto->Version = NDIS_TASK_OFFLOAD_VERSION;
                    pto->Size = sizeof(*pto);
                    *pOffset = RtlPointerToOffset(base, pto);
                    base = pto;
                    pOffset = &pto->OffsetNextTask;
                    switch(i)
                    {
                        case 1:
                        {
                            NDIS_TASK_TCP_IP_CHECKSUM *pcs = (NDIS_TASK_TCP_IP_CHECKSUM *)pto->TaskBuffer;
                            pto->Task = TcpIpChecksumNdisTask;
                            pto->TaskBufferLength = sizeof(*pcs);
                            NdisMoveMemory(pcs, &cs, sizeof(cs));
                            pto = (NDIS_TASK_OFFLOAD *)(pcs + 1);
                            break;
                        }
                        case 0:
                        {
                            NDIS_TASK_TCP_LARGE_SEND  *pls = (NDIS_TASK_TCP_LARGE_SEND *)pto->TaskBuffer;
                            pto->Task = TcpLargeSendNdisTask;
                            pto->TaskBufferLength = sizeof(*pls);
                            NdisMoveMemory(pls, &lso, sizeof(lso));
                            pto = (NDIS_TASK_OFFLOAD *)(pls + 1);
                            break;
                        }
                        default:
                            break;
                    }
                }
                ++i;
            } while (flags);
            status = ParseOffload(pContext, ph, size, FALSE, reason, FALSE);
        }
        else
        {
            NdisFreeMemory(*ppInfo, 0, 0);
            *ppInfo = NULL;
            status = NDIS_STATUS_NOT_SUPPORTED;
        }
    }
    return status;
}


NDIS_STATUS CreateOffloadInfo5ForQuery(
    PARANDIS_ADAPTER *pContext,
    tOidDesc *pOid,
    PVOID *ppInfo,
    PULONG pulSize)
{
    NDIS_TASK_OFFLOAD_HEADER *pth = (NDIS_TASK_OFFLOAD_HEADER *)pOid->InformationBuffer;
    NDIS_STATUS status;
    *ppInfo  = NULL;
    *pulSize = 0;
    if (pOid->InformationBufferLength < sizeof(*pth)) pth = &ReservedHeader;
    status = ParseOffload(pContext, pth, pOid->InformationBufferLength, FALSE, "query enter", TRUE);
    if (status == NDIS_STATUS_SUCCESS)
    {
        CreateOffloadInfo5Internal(pContext, ppInfo, pulSize, "QUERY", pth);
    }
    return status;
}

NDIS_STATUS OnOidSetNdis5Offload(PARANDIS_ADAPTER *pContext, tOidDesc *pOid)
{
    NDIS_STATUS status;
    status = ParseOffload(pContext, (NDIS_TASK_OFFLOAD_HEADER *)pOid->InformationBuffer,
        pOid->InformationBufferLength, TRUE, "SET", FALSE);
    if (status == STATUS_SUCCESS)
    {
#if 0   // only for logging after SET
        PVOID pInfo = NULL;
        ULONG dummy = 0;
        CreateOffloadInfo5Internal(pContext, &pInfo, &dummy, "UPDATED", &ReservedHeader);
        if (pInfo) NdisFreeMemory(pInfo, 0, 0);
#endif
        *pOid->pBytesRead = pOid->InformationBufferLength;
    }
    else
    {
        DPrintf(0, ("[%s], restoring after unsuccessful set", __FUNCTION__));
        pContext->Offload = pContext->Offload;
    }
    return status;
}
