1285 lines
54 KiB
C
1285 lines
54 KiB
C
|
#include <precomp.h>
|
||
|
#include "wzcsvc.h"
|
||
|
#include "intflist.h"
|
||
|
#include "utils.h"
|
||
|
#include "deviceio.h"
|
||
|
#include "storage.h"
|
||
|
#include "state.h"
|
||
|
#include "notify.h"
|
||
|
#include "dialog.h"
|
||
|
#include "tracing.h"
|
||
|
|
||
|
//-----------------------------------------------------------
|
||
|
// StateTmSetOneTimeTimer: Sets a one time timer for the given context with the
|
||
|
// hardcoded callback WZCTimeoutCallback() and with the parameter the interface
|
||
|
// context itself.
|
||
|
// Parameters:
|
||
|
// [in/out] pIntfContext: identifies the context for which is set the timer.
|
||
|
// [in] dwMSeconds: miliseconds interval when the timer is due to fire
|
||
|
DWORD
|
||
|
StateTmSetOneTimeTimer(
|
||
|
PINTF_CONTEXT pIntfContext,
|
||
|
DWORD dwMSeconds)
|
||
|
{
|
||
|
DWORD dwErr = ERROR_SUCCESS;
|
||
|
|
||
|
if (pIntfContext->hTimer == INVALID_HANDLE_VALUE)
|
||
|
{
|
||
|
dwErr = ERROR_INVALID_PARAMETER;
|
||
|
}
|
||
|
else if (ChangeTimerQueueTimer(
|
||
|
g_htmQueue,
|
||
|
pIntfContext->hTimer,
|
||
|
dwMSeconds,
|
||
|
TMMS_INFINITE))
|
||
|
{
|
||
|
if (dwMSeconds == TMMS_INFINITE)
|
||
|
pIntfContext->dwCtlFlags &= ~INTFCTL_INTERNAL_TM_ON;
|
||
|
else
|
||
|
pIntfContext->dwCtlFlags |= INTFCTL_INTERNAL_TM_ON;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
DbgPrint((TRC_SYNC, "Failed setting the context 0x%p timer to %dms", pIntfContext, dwMSeconds));
|
||
|
dwErr = GetLastError();
|
||
|
}
|
||
|
|
||
|
return dwErr;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------
|
||
|
// StateDispatchEvent: processes an event that will cause the state machine to transition
|
||
|
// through one or more states.
|
||
|
// Parameters:
|
||
|
// [in] StateEvent: identifies the event that triggers the transition(s)
|
||
|
// [in] pIntfContext: points to the interface that is subject for the transition(s)
|
||
|
// [in] pvEventData: any data related to the event
|
||
|
// NOTE: The caller of this function should already take care of locking the pIntfContext
|
||
|
// in its critical section. The assumption is the Interface Context is already locked.
|
||
|
DWORD
|
||
|
StateDispatchEvent(
|
||
|
ESTATE_EVENT StateEvent,
|
||
|
PINTF_CONTEXT pIntfContext,
|
||
|
PVOID pvEventData)
|
||
|
{
|
||
|
DWORD dwErr = ERROR_SUCCESS;
|
||
|
|
||
|
DbgPrint((TRC_TRACK|TRC_STATE,"[StateDispatchEvent(%d,0x%p,0x%p)", StateEvent, pIntfContext, pvEventData));
|
||
|
DbgAssert((pIntfContext != NULL, "Can't dispatch event for NULL context!"));
|
||
|
|
||
|
// determine the state to transition to, based on the event that is to be dispatched
|
||
|
// whatever the current state is, if the event is eEventAdd, move the context to SI
|
||
|
switch(StateEvent)
|
||
|
{
|
||
|
case eEventAdd:
|
||
|
//Record Event into logging DB
|
||
|
DbLogWzcInfo(WZCSVC_EVENT_ADD, pIntfContext,
|
||
|
pIntfContext->wszDescr);
|
||
|
// on interface addition, no matter what, go straight to {SI}
|
||
|
pIntfContext->pfnStateHandler = StateInitFn;
|
||
|
dwErr = ERROR_CONTINUE;
|
||
|
break;
|
||
|
case eEventTimeout:
|
||
|
// if timeout in {SSr}, transition to {SQ}
|
||
|
if (pIntfContext->pfnStateHandler == StateSoftResetFn)
|
||
|
{
|
||
|
pIntfContext->pfnStateHandler = StateQueryFn;
|
||
|
dwErr = ERROR_CONTINUE;
|
||
|
}
|
||
|
// if timeout in {SDSr}, transition back to {SSr}
|
||
|
else if (pIntfContext->pfnStateHandler == StateDelaySoftResetFn)
|
||
|
{
|
||
|
pIntfContext->pfnStateHandler = StateSoftResetFn;
|
||
|
dwErr = ERROR_CONTINUE;
|
||
|
}
|
||
|
// if timeout in {SF}, transition to {SHr}
|
||
|
else if (pIntfContext->pfnStateHandler == StateFailedFn)
|
||
|
{
|
||
|
pIntfContext->pfnStateHandler = StateHardResetFn;
|
||
|
dwErr = ERROR_CONTINUE;
|
||
|
}
|
||
|
// if timeout in {SIter}, transition to {SRs}
|
||
|
else if (pIntfContext->pfnStateHandler == StateIterateFn)
|
||
|
{
|
||
|
pIntfContext->pfnStateHandler = StateCfgRemoveFn;
|
||
|
dwErr = ERROR_CONTINUE;
|
||
|
}
|
||
|
// if timeout in {SC} or {SCk}, transition to {SSr}
|
||
|
else if (pIntfContext->pfnStateHandler == StateConfiguredFn ||
|
||
|
pIntfContext->pfnStateHandler == StateCfgHardKeyFn)
|
||
|
{
|
||
|
pIntfContext->pfnStateHandler = StateSoftResetFn;
|
||
|
dwErr = ERROR_CONTINUE;
|
||
|
}
|
||
|
// Record Event into logging DB
|
||
|
DbLogWzcInfo(WZCSVC_EVENT_TIMEOUT, pIntfContext);
|
||
|
break;
|
||
|
case eEventConnect:
|
||
|
// for each media connect notification, read the BSSID
|
||
|
// Don't care if it fails (it might in certain cases)
|
||
|
dwErr = DevioRefreshIntfOIDs(pIntfContext, INTF_BSSID, NULL);
|
||
|
|
||
|
// Record Event into logging DB - this should be the first log showing up
|
||
|
DbLogWzcInfo(WZCSVC_EVENT_CONNECT, pIntfContext);
|
||
|
|
||
|
// if there was any error getting the BSSID, log the error here
|
||
|
if (dwErr != ERROR_SUCCESS)
|
||
|
DbLogWzcError(WZCSVC_ERR_QUERRY_BSSID, pIntfContext, dwErr);
|
||
|
|
||
|
// if there is a chance the association is alredy successful
|
||
|
// reset the session keys - if applicable
|
||
|
if (dwErr == ERROR_SUCCESS &&
|
||
|
(pIntfContext->pfnStateHandler == StateConfiguredFn ||
|
||
|
pIntfContext->pfnStateHandler == StateCfgHardKeyFn ||
|
||
|
pIntfContext->pfnStateHandler == StateSoftResetFn ||
|
||
|
pIntfContext->pfnStateHandler == StateQueryFn
|
||
|
)
|
||
|
)
|
||
|
{
|
||
|
dwErr = LstGenInitialSessionKeys(pIntfContext);
|
||
|
|
||
|
// if there was any error setting the initial session keys, log it here
|
||
|
if (dwErr != ERROR_SUCCESS)
|
||
|
DbLogWzcError(WZCSVC_ERR_GEN_SESSION_KEYS, pIntfContext, dwErr);
|
||
|
}
|
||
|
|
||
|
// reset the error id since nothing that happens so far
|
||
|
// is critical enough to stop the state machine.
|
||
|
dwErr = ERROR_SUCCESS;
|
||
|
|
||
|
// if connect in {SIter}, transition to {SN}
|
||
|
if (pIntfContext->pfnStateHandler == StateIterateFn)
|
||
|
{
|
||
|
pIntfContext->pfnStateHandler = StateNotifyFn;
|
||
|
dwErr = ERROR_CONTINUE;
|
||
|
}
|
||
|
break;
|
||
|
case eEventDisconnect:
|
||
|
//Record Event into logging DB
|
||
|
DbLogWzcInfo(WZCSVC_EVENT_DISCONNECT, pIntfContext);
|
||
|
if (pIntfContext->pfnStateHandler == StateSoftResetFn ||
|
||
|
pIntfContext->pfnStateHandler == StateConfiguredFn ||
|
||
|
pIntfContext->pfnStateHandler == StateCfgHardKeyFn)
|
||
|
{
|
||
|
pIntfContext->pfnStateHandler = StateHardResetFn;
|
||
|
dwErr = ERROR_CONTINUE;
|
||
|
}
|
||
|
break;
|
||
|
case eEventCmdRefresh:
|
||
|
//Record Event into logging DB
|
||
|
DbLogWzcInfo(WZCSVC_EVENT_CMDREFRESH, pIntfContext);
|
||
|
if (pvEventData == NULL)
|
||
|
{
|
||
|
dwErr = ERROR_INVALID_PARAMETER;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
DWORD dwFlags = *(LPDWORD)pvEventData;
|
||
|
|
||
|
// no matter the state this context is in, if it is not configured
|
||
|
// successfully or during a scan cycle, it will be transitioned to {SHr}
|
||
|
// (need to clear the bvsList, otherwise it could mistakenly land in {SC}, on the
|
||
|
// path {SSr}->{SQ}->{SC}.
|
||
|
if (pIntfContext->pfnStateHandler != StateConfiguredFn &&
|
||
|
pIntfContext->pfnStateHandler != StateCfgHardKeyFn &&
|
||
|
pIntfContext->pfnStateHandler != StateSoftResetFn &&
|
||
|
pIntfContext->pfnStateHandler != StateDelaySoftResetFn)
|
||
|
{
|
||
|
pIntfContext->pfnStateHandler = StateHardResetFn;
|
||
|
dwErr = ERROR_CONTINUE;
|
||
|
}
|
||
|
// if the context is already configured, then we need to either go directly to
|
||
|
// {SSr} if a scan is requested or just to {SQ} if no scan is needed. In the latter
|
||
|
// case, the OIDs will be loaded, and because of the visible list which most probably
|
||
|
// won't be changed (no new scanned happening in between) the context will be
|
||
|
// transitioned instantly back to {SC}
|
||
|
else if (pIntfContext->pfnStateHandler == StateConfiguredFn ||
|
||
|
pIntfContext->pfnStateHandler == StateCfgHardKeyFn)
|
||
|
{
|
||
|
// the refresh command caught the context in {SC} state
|
||
|
// if a scan is requested, transition to {SSr} or
|
||
|
pIntfContext->pfnStateHandler = (dwFlags & INTF_LIST_SCAN) ? StateSoftResetFn : StateQueryFn;
|
||
|
dwErr = ERROR_CONTINUE;
|
||
|
}
|
||
|
// if the context is already in {SSr} or {SDSr} then a scan & full query will
|
||
|
// happen in a matter of seconds. So just return SUCCESS to the call with no other
|
||
|
// action to take.
|
||
|
}
|
||
|
break;
|
||
|
case eEventCmdReset:
|
||
|
//Record Event into logging DB
|
||
|
DbLogWzcInfo(WZCSVC_EVENT_CMDRESET, pIntfContext);
|
||
|
// When this happens, also clean up the blocked list. Any user configuration change should give
|
||
|
// another chance to configurations previously blocked.
|
||
|
WzcCleanupWzcList(pIntfContext->pwzcBList);
|
||
|
pIntfContext->pwzcBList = NULL;
|
||
|
// if reset is requested, no matter what, transition to {SHr}
|
||
|
pIntfContext->pfnStateHandler = StateHardResetFn;
|
||
|
dwErr = ERROR_CONTINUE;
|
||
|
break;
|
||
|
case eEventCmdCfgDelete:
|
||
|
case eEventCmdCfgNext:
|
||
|
//Record Event into logging DB
|
||
|
DbLogWzcInfo((StateEvent == eEventCmdCfgDelete? WZCSVC_EVENT_CMDCFGDELETE : WZCSVC_EVENT_CMDCFGNEXT), pIntfContext);
|
||
|
if (pIntfContext->pfnStateHandler == StateConfiguredFn ||
|
||
|
pIntfContext->pfnStateHandler == StateCfgHardKeyFn ||
|
||
|
pIntfContext->pfnStateHandler == StateSoftResetFn)
|
||
|
{
|
||
|
if (StateEvent == eEventCmdCfgDelete)
|
||
|
{
|
||
|
// mark in the control bits that this removal is forced
|
||
|
pIntfContext->dwCtlFlags |= INTFCTL_INTERNAL_FORCE_CFGREM;
|
||
|
pIntfContext->pfnStateHandler = StateCfgRemoveFn;
|
||
|
}
|
||
|
else
|
||
|
pIntfContext->pfnStateHandler = StateCfgPreserveFn;
|
||
|
|
||
|
dwErr = ERROR_CONTINUE;
|
||
|
}
|
||
|
break;
|
||
|
case eEventCmdCfgNoop:
|
||
|
//Record Event into logging DB
|
||
|
DbLogWzcInfo(WZCSVC_EVENT_CMDCFGNOOP, pIntfContext);
|
||
|
if (pIntfContext->pfnStateHandler == StateCfgHardKeyFn)
|
||
|
{
|
||
|
// mark in the control bits that this removal is forced
|
||
|
pIntfContext->dwCtlFlags |= INTFCTL_INTERNAL_FORCE_CFGREM;
|
||
|
pIntfContext->pfnStateHandler = StateCfgRemoveFn;
|
||
|
dwErr = ERROR_CONTINUE;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// if this event is not going to be ignored, dwErr is ERROR_CONTINUE at this point.
|
||
|
// otherwise is ERROR_SUCCESS.
|
||
|
// So, if this event is NOT going to be ignored, reset whatever timer
|
||
|
// might have had for the related context. Keep in mind this call is already locking
|
||
|
// the context so if the timer fired already, there is no sync problem
|
||
|
if (dwErr == ERROR_CONTINUE)
|
||
|
{
|
||
|
TIMER_RESET(pIntfContext, dwErr);
|
||
|
|
||
|
// restore the "continue" in case of success
|
||
|
if (dwErr == ERROR_SUCCESS)
|
||
|
dwErr = ERROR_CONTINUE;
|
||
|
}
|
||
|
|
||
|
// if the event is to be processed, dwErr is ERROR_CONTINUE.
|
||
|
// In order to handle the automatic transitions, each State Handler function should set
|
||
|
// the pfnStateHandler field to the state handler where the automatic transition goes and
|
||
|
// also it should return ERROR_CONTINUE. Any other error code means the current
|
||
|
// processing stops. Future transitions will be triggered by future event/timer timeout.
|
||
|
while (dwErr == ERROR_CONTINUE)
|
||
|
{
|
||
|
dwErr = (*(pIntfContext->pfnStateHandler))(pIntfContext);
|
||
|
}
|
||
|
|
||
|
DbgPrint((TRC_TRACK|TRC_STATE,"StateDispatchEvent]=%d", dwErr));
|
||
|
return dwErr;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------
|
||
|
// StateInitFn: Handler for the Init State.
|
||
|
// This function runs in the context's critical section
|
||
|
DWORD
|
||
|
StateInitFn(PINTF_CONTEXT pIntfContext)
|
||
|
{
|
||
|
DWORD dwErr = ERROR_SUCCESS;
|
||
|
|
||
|
DbgPrint((TRC_TRACK|TRC_STATE,"[StateInitFn(0x%p)", pIntfContext));
|
||
|
DbgAssert((pIntfContext != NULL,"Invalid NULL context in {SI} state"));
|
||
|
|
||
|
//Record current state into logging DB
|
||
|
DbLogWzcInfo(WZCSVC_SM_STATE_INIT, pIntfContext, pIntfContext->wszDescr);
|
||
|
// for this new interface, load its settings from the registry
|
||
|
dwErr = StoLoadIntfConfig(NULL, pIntfContext);
|
||
|
DbgAssert((dwErr == ERROR_SUCCESS,
|
||
|
"StoLoadIntfConfig failed for Intf context 0x%p",
|
||
|
pIntfContext));
|
||
|
|
||
|
if (dwErr == ERROR_SUCCESS && g_wzcInternalCtxt.bValid)
|
||
|
{
|
||
|
PINTF_CONTEXT pIntfTContext;
|
||
|
// apply the global template to this newly created interface
|
||
|
EnterCriticalSection(&g_wzcInternalCtxt.csContext);
|
||
|
pIntfTContext = g_wzcInternalCtxt.pIntfTemplate;
|
||
|
LstRccsReference(pIntfTContext);
|
||
|
LeaveCriticalSection(&g_wzcInternalCtxt.csContext);
|
||
|
|
||
|
LstRccsLock(pIntfTContext);
|
||
|
dwErr = LstApplyTemplate(
|
||
|
pIntfTContext,
|
||
|
pIntfContext,
|
||
|
NULL);
|
||
|
LstRccsUnlockUnref(pIntfTContext);
|
||
|
}
|
||
|
|
||
|
if (dwErr == ERROR_SUCCESS)
|
||
|
{
|
||
|
// getting the interface status (media type & media state)
|
||
|
dwErr = DevioGetIntfStats(pIntfContext);
|
||
|
DbgAssert((dwErr == ERROR_SUCCESS,
|
||
|
"DevioGetIntfStats failed for Intf context 0x%p",
|
||
|
pIntfContext));
|
||
|
|
||
|
// getting the interface MAC address
|
||
|
dwErr = DevioGetIntfMac(pIntfContext);
|
||
|
DbgAssert((dwErr == ERROR_SUCCESS,
|
||
|
"DevioGetIntfMac failed for Intf context 0x%p",
|
||
|
pIntfContext));
|
||
|
DbgBinPrint((TRC_TRACK, "Local Mac address :",
|
||
|
pIntfContext->ndLocalMac, sizeof(NDIS_802_11_MAC_ADDRESS)));
|
||
|
}
|
||
|
|
||
|
// fail initialization if the interface is not a wireless adapter
|
||
|
if (dwErr == ERROR_SUCCESS &&
|
||
|
pIntfContext->ulPhysicalMediaType != NdisPhysicalMediumWirelessLan)
|
||
|
dwErr = ERROR_MEDIA_INCOMPATIBLE;
|
||
|
|
||
|
// do a preliminary check on the OIDs
|
||
|
if (dwErr == ERROR_SUCCESS)
|
||
|
{
|
||
|
DWORD dwLErr;
|
||
|
|
||
|
dwLErr = DevioRefreshIntfOIDs(
|
||
|
pIntfContext,
|
||
|
INTF_INFRAMODE|INTF_AUTHMODE|INTF_WEPSTATUS|INTF_SSID|INTF_BSSIDLIST,
|
||
|
NULL);
|
||
|
// if the query succeeded, then assume the NIC supports the OIDs needed for Zero Config
|
||
|
if (dwLErr == ERROR_SUCCESS)
|
||
|
{
|
||
|
pIntfContext->dwCtlFlags |= INTFCTL_OIDSSUPP;
|
||
|
}
|
||
|
// otherwise don't make this determination now - it could be a failure caused by the
|
||
|
// device booting up.
|
||
|
}
|
||
|
|
||
|
// if all went well, prepare an automatic transition to {SHr}
|
||
|
if (dwErr == ERROR_SUCCESS)
|
||
|
{
|
||
|
// set the "signal" control bit
|
||
|
pIntfContext->dwCtlFlags |= INTFCTL_INTERNAL_SIGNAL;
|
||
|
pIntfContext->pfnStateHandler = StateHardResetFn;
|
||
|
|
||
|
// at this point, if the service is not enabled on this wireless interface
|
||
|
// Notify the stack (TCP) of the failure in order to unblock the NetReady notification.
|
||
|
if (!(pIntfContext->dwCtlFlags & INTFCTL_ENABLED) &&
|
||
|
!(pIntfContext->dwCtlFlags & INTFCTL_INTERNAL_INITFAILNOTIF))
|
||
|
{
|
||
|
// call into the stack - don't care about the return code.
|
||
|
DevioNotifyFailure(pIntfContext->wszGuid);
|
||
|
// make sure this call is never done twice for this adapter
|
||
|
pIntfContext->dwCtlFlags |= INTFCTL_INTERNAL_INITFAILNOTIF;
|
||
|
}
|
||
|
|
||
|
dwErr = ERROR_CONTINUE;
|
||
|
}
|
||
|
|
||
|
DbgPrint((TRC_TRACK|TRC_STATE,"StateInitFn]=%d", dwErr));
|
||
|
return dwErr;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------
|
||
|
// StateHardResetFn: Handler for the {SHr} state
|
||
|
DWORD
|
||
|
StateHardResetFn(PINTF_CONTEXT pIntfContext)
|
||
|
{
|
||
|
DWORD dwErr = ERROR_SUCCESS;
|
||
|
|
||
|
DbgPrint((TRC_TRACK|TRC_STATE,"[StateHardResetFn(0x%p)", pIntfContext));
|
||
|
DbgAssert((pIntfContext != NULL,"Invalid NULL context in {SHr} state"));
|
||
|
|
||
|
// in this state we are surely not associated.
|
||
|
ZeroMemory(pIntfContext->wzcCurrent.MacAddress, sizeof(NDIS_802_11_MAC_ADDRESS));
|
||
|
// Record current state into logging DB
|
||
|
DbLogWzcInfo(WZCSVC_SM_STATE_HARDRESET, pIntfContext);
|
||
|
// once in this state, the ncstatus should report "connecting"
|
||
|
pIntfContext->ncStatus = NCS_CONNECTING;
|
||
|
// if the service is enabled, notify netman about the current ncstatus
|
||
|
if (pIntfContext->dwCtlFlags & INTFCTL_ENABLED)
|
||
|
WzcNetmanNotify(pIntfContext);
|
||
|
|
||
|
// Bump up the session handler for this intf context,
|
||
|
// since it starts plumbing new configs. No commands from older
|
||
|
// iterations should be accepted from now on.
|
||
|
pIntfContext->dwSessionHandle++;
|
||
|
|
||
|
// on hard reset, reopen NDISUIO handle and get the current SSID
|
||
|
dwErr = DevioRefreshIntfOIDs(
|
||
|
pIntfContext,
|
||
|
INTF_HANDLE|INTF_SSID,
|
||
|
NULL);
|
||
|
// ignore whatever error is encountered here..
|
||
|
// If there is an error, then the interface handle will be invalid
|
||
|
// and it will be internally reopened in the {SSr} state
|
||
|
|
||
|
// at this point make sure the card won't get randomly associate
|
||
|
// during the subsequent network scan. We make this happen by plumbing
|
||
|
// down a random non-visible SSID but only in the following cases:
|
||
|
// - the service is enabled (otherwise no config change is allowed)
|
||
|
// - the current SSID was retrieved successfully
|
||
|
// - there is an SSID returned by the driver
|
||
|
// - the current SSID shows as being NULL (all filled with 0 chars)
|
||
|
if (pIntfContext->dwCtlFlags & INTFCTL_ENABLED &&
|
||
|
dwErr == ERROR_SUCCESS &&
|
||
|
WzcIsNullBuffer(pIntfContext->wzcCurrent.Ssid.Ssid, pIntfContext->wzcCurrent.Ssid.SsidLength))
|
||
|
{
|
||
|
BYTE chSSID[32];
|
||
|
|
||
|
DbgPrint((TRC_STATE,"Overwriting null SSID before scan"));
|
||
|
|
||
|
ZeroMemory(&chSSID, sizeof(chSSID));
|
||
|
if (WzcRndGenBuffer(chSSID, 32, 1, 31) == ERROR_SUCCESS)
|
||
|
{
|
||
|
INTF_ENTRY IntfEntry;
|
||
|
|
||
|
ZeroMemory(&IntfEntry, sizeof(INTF_ENTRY));
|
||
|
IntfEntry.rdSSID.pData = chSSID;
|
||
|
IntfEntry.rdSSID.dwDataLen = 32;
|
||
|
IntfEntry.nInfraMode = Ndis802_11Infrastructure;
|
||
|
|
||
|
DevioSetIntfOIDs(
|
||
|
pIntfContext,
|
||
|
&IntfEntry,
|
||
|
INTF_SSID | INTF_INFRAMODE,
|
||
|
NULL);
|
||
|
|
||
|
// this is not an SSID we need to remember (being a random one)
|
||
|
ZeroMemory(&(pIntfContext->wzcCurrent.Ssid), sizeof(NDIS_802_11_SSID));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// on hard reset, free the current selection list. This way,
|
||
|
// whatever new selection list we build later will have all
|
||
|
// new networks and a configuration will be forcefully plumbed
|
||
|
WzcCleanupWzcList(pIntfContext->pwzcSList);
|
||
|
pIntfContext->pwzcSList = NULL;
|
||
|
|
||
|
// automatic transition to {SSr} state
|
||
|
pIntfContext->pfnStateHandler = StateSoftResetFn;
|
||
|
dwErr = ERROR_CONTINUE;
|
||
|
|
||
|
DbgPrint((TRC_TRACK|TRC_STATE,"StateHardResetFn]=%d", dwErr));
|
||
|
return dwErr;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------
|
||
|
// StateSoftResetFn: Handler for the {SSr} state
|
||
|
DWORD
|
||
|
StateSoftResetFn(PINTF_CONTEXT pIntfContext)
|
||
|
{
|
||
|
DWORD dwErr = ERROR_SUCCESS;
|
||
|
|
||
|
DbgPrint((TRC_TRACK|TRC_STATE,"[StateSoftResetFn(0x%p)", pIntfContext));
|
||
|
DbgAssert((pIntfContext != NULL,"Invalid NULL context in {SSr} state"));
|
||
|
|
||
|
//Record current state into logging DB
|
||
|
DbLogWzcInfo(WZCSVC_SM_STATE_SOFTRESET, pIntfContext);
|
||
|
|
||
|
DbgPrint((TRC_STATE,"Delay {SSr} on failure? %s",
|
||
|
(pIntfContext->dwCtlFlags & INTFCTL_INTERNAL_NO_DELAY) ? "No" : "Yes"));
|
||
|
|
||
|
// indicate to the driver to rescan the BSSID_LIST for this adapter
|
||
|
dwErr = DevioRefreshIntfOIDs(
|
||
|
pIntfContext,
|
||
|
INTF_LIST_SCAN,
|
||
|
NULL);
|
||
|
if (dwErr == ERROR_SUCCESS)
|
||
|
{
|
||
|
// once we passed through this state, allow again delayed
|
||
|
// execution of {SSr} in future loops.
|
||
|
pIntfContext->dwCtlFlags &= ~INTFCTL_INTERNAL_NO_DELAY;
|
||
|
// set the rescan timer
|
||
|
TIMER_SET(pIntfContext, TMMS_Tr, dwErr);
|
||
|
// when the timer will be fired off, the dispatcher will
|
||
|
// take care to transit this context into the {SQ} state.
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// it happens that after resume from standby WZC is waken up before the
|
||
|
// adapter being bound correctly to NDISUIO in which case, scanning the networks
|
||
|
// return ERROR_NOT_SUPPORTED. So, just to play safe, in case of any error just give
|
||
|
// it another try in a couple of seconds.
|
||
|
if (!(pIntfContext->dwCtlFlags & INTFCTL_INTERNAL_NO_DELAY))
|
||
|
{
|
||
|
// once we passed through this state, don't allow further delayed execution
|
||
|
pIntfContext->dwCtlFlags |= INTFCTL_INTERNAL_NO_DELAY;
|
||
|
pIntfContext->pfnStateHandler = StateDelaySoftResetFn;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// once we passed through this state, allow again delayed
|
||
|
// execution of {SSr} in future loops
|
||
|
// also, if the OIDs are failing, don't pop up any balloon.
|
||
|
pIntfContext->dwCtlFlags &= ~(INTFCTL_INTERNAL_NO_DELAY|INTFCTL_INTERNAL_SIGNAL);
|
||
|
// regardless the error, just go over it and assume the driver has already the list
|
||
|
// of SSIDs and all the other OIDs we need. Will use that one hence we have to go on to {SQ}.
|
||
|
pIntfContext->pfnStateHandler = StateQueryFn;
|
||
|
}
|
||
|
dwErr = ERROR_CONTINUE;
|
||
|
}
|
||
|
|
||
|
DbgPrint((TRC_TRACK|TRC_STATE,"StateSoftResetFn]=%d", dwErr));
|
||
|
return dwErr;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------
|
||
|
// StateDelaySoftResetFn: Handler for the {SDSr} state
|
||
|
DWORD
|
||
|
StateDelaySoftResetFn(
|
||
|
PINTF_CONTEXT pIntfContext)
|
||
|
{
|
||
|
DWORD dwErr = ERROR_SUCCESS;
|
||
|
|
||
|
DbgPrint((TRC_TRACK|TRC_STATE,"[StateDelaySoftResetFn(0x%p)", pIntfContext));
|
||
|
DbgAssert((pIntfContext != NULL,"Invalid NULL context in {SDSr} state"));
|
||
|
|
||
|
DbLogWzcInfo(WZCSVC_SM_STATE_DELAY_SR, pIntfContext);
|
||
|
// if there was a failure in {SSr} such that we had to delay and retry that state
|
||
|
// then refresh the interface handle in an attempt to recover from the error
|
||
|
DevioRefreshIntfOIDs(
|
||
|
pIntfContext,
|
||
|
INTF_HANDLE,
|
||
|
NULL);
|
||
|
|
||
|
// set the timer to retry the {SSr} state
|
||
|
TIMER_SET(pIntfContext, TMMS_Td, dwErr);
|
||
|
|
||
|
DbgPrint((TRC_TRACK|TRC_STATE,"StateDelaySoftResetFn]=%d", dwErr));
|
||
|
return dwErr;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------
|
||
|
// StateQueryFn: Handler for the {SQ} state
|
||
|
DWORD
|
||
|
StateQueryFn(PINTF_CONTEXT pIntfContext)
|
||
|
{
|
||
|
DWORD dwErr = ERROR_SUCCESS;
|
||
|
|
||
|
DbgPrint((TRC_TRACK|TRC_STATE,"[StateQueryFn(0x%p)", pIntfContext));
|
||
|
DbgAssert((pIntfContext != NULL,"Invalid NULL context in {SQ} state"));
|
||
|
DbgAssert((pIntfContext->hIntf != INVALID_HANDLE_VALUE,"Invalid Ndisuio handle in {SQ} state"));
|
||
|
//Record current state into logging DB
|
||
|
DbLogWzcInfo(WZCSVC_SM_STATE_QUERY, pIntfContext);
|
||
|
|
||
|
dwErr = DevioGetIntfStats(pIntfContext);
|
||
|
// don't care much about the result of this call..
|
||
|
DbgAssert((dwErr == ERROR_SUCCESS, "Getting NDIS statistics failed in state {SQ}"));
|
||
|
// check the media state (just for debugging)
|
||
|
DbgPrint((TRC_GENERIC, "Media State (%d) is %s",
|
||
|
pIntfContext->ulMediaState,
|
||
|
pIntfContext->ulMediaState == MEDIA_STATE_CONNECTED ? "Connected" : "Not Connected"));
|
||
|
|
||
|
dwErr = DevioRefreshIntfOIDs(
|
||
|
pIntfContext,
|
||
|
INTF_INFRAMODE|INTF_AUTHMODE|INTF_WEPSTATUS|INTF_SSID|INTF_BSSIDLIST,
|
||
|
NULL);
|
||
|
|
||
|
// dwErr is success only in the case all the queries above succeeded
|
||
|
if (dwErr == ERROR_SUCCESS)
|
||
|
{
|
||
|
PWZC_802_11_CONFIG_LIST pwzcSList = NULL;
|
||
|
|
||
|
pIntfContext->dwCtlFlags |= INTFCTL_OIDSSUPP;
|
||
|
|
||
|
// deprecate entries in the blocked list according to the new visible list.
|
||
|
// Entries in the BList which are not seen as "visible" for WZC_INTERNAL_BLOCKED_TTL
|
||
|
// number of times are taken out of that list.
|
||
|
// this function can't fail, this is why we don't check its return value.
|
||
|
LstDeprecateBlockedList(pIntfContext);
|
||
|
|
||
|
// build the pwzcSList out of pwzcVList and pwzcPList and
|
||
|
// taking into consideration the fallback flag
|
||
|
dwErr = LstBuildSelectList(pIntfContext, &pwzcSList);
|
||
|
|
||
|
if (dwErr == ERROR_SUCCESS)
|
||
|
{
|
||
|
UINT nSelIdx = 0;
|
||
|
|
||
|
// check the new selection list against the previous one and see
|
||
|
// if a new plumbing is required or not.
|
||
|
if (LstChangedSelectList(pIntfContext, pwzcSList, &nSelIdx))
|
||
|
{
|
||
|
// if a new plumbing is required, get rid of the old selection
|
||
|
// list and put the new one in the interface context.
|
||
|
WzcCleanupWzcList(pIntfContext->pwzcSList);
|
||
|
pIntfContext->pwzcSList = pwzcSList;
|
||
|
// Make sure we default clear this flag - it will be set a bit down if
|
||
|
// indeed this turns out to be a "one time" deal.
|
||
|
pIntfContext->dwCtlFlags &= ~INTFCTL_INTERNAL_ONE_TIME;
|
||
|
// if the preferred list also includes a starting index, then this
|
||
|
// is a request for a "one time configuration"
|
||
|
if (pIntfContext->pwzcPList != NULL &&
|
||
|
pIntfContext->pwzcPList->Index < pIntfContext->pwzcPList->NumberOfItems)
|
||
|
{
|
||
|
// reset the "start from" index in the preferred list as this is intended
|
||
|
// for "one time configuration" mostly.
|
||
|
pIntfContext->pwzcPList->Index = pIntfContext->pwzcPList->NumberOfItems;
|
||
|
// but keep in mind this is a one time configuration
|
||
|
pIntfContext->dwCtlFlags |= INTFCTL_INTERNAL_ONE_TIME;
|
||
|
}
|
||
|
|
||
|
// set the starting index in the selection list, if there is one
|
||
|
if (pIntfContext->pwzcSList != NULL)
|
||
|
pIntfContext->pwzcSList->Index = nSelIdx;
|
||
|
|
||
|
// then go to the {SIter}
|
||
|
pIntfContext->pfnStateHandler = StateIterateFn;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
PWZC_WLAN_CONFIG pConfig;
|
||
|
|
||
|
pConfig = &(pIntfContext->pwzcSList->Config[pIntfContext->pwzcSList->Index]);
|
||
|
DbLogWzcInfo(WZCSVC_SM_STATE_QUERY_NOCHANGE, pIntfContext,
|
||
|
DbLogFmtSSID(0, &(pConfig->Ssid)));
|
||
|
|
||
|
// if no new plumbing is required clean the new selection list
|
||
|
WzcCleanupWzcList(pwzcSList);
|
||
|
// then go to {SC} or {SCk} (depending the WEP key) since the interface context
|
||
|
// has not changed a bit. The selection list & the selection index were not
|
||
|
// touched in this cycle
|
||
|
if (pIntfContext->dwCtlFlags & INTFCTL_INTERNAL_FAKE_WKEY)
|
||
|
pIntfContext->pfnStateHandler = StateCfgHardKeyFn;
|
||
|
else
|
||
|
pIntfContext->pfnStateHandler = StateConfiguredFn;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// since the oids failed, suppress any subsequent balloon:
|
||
|
pIntfContext->dwCtlFlags &= ~(INTFCTL_OIDSSUPP|INTFCTL_INTERNAL_SIGNAL);
|
||
|
pIntfContext->pfnStateHandler = StateFailedFn;
|
||
|
}
|
||
|
|
||
|
// in both cases, this is an automatic transition
|
||
|
dwErr = ERROR_CONTINUE;
|
||
|
|
||
|
DbgPrint((TRC_TRACK|TRC_STATE,"StateQueryFn]=%d", dwErr));
|
||
|
return dwErr;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------
|
||
|
// StateIterateFn: Handler for the {SIter} state
|
||
|
DWORD
|
||
|
StateIterateFn(
|
||
|
PINTF_CONTEXT pIntfContext)
|
||
|
{
|
||
|
DWORD dwErr = ERROR_SUCCESS;
|
||
|
PWZC_WLAN_CONFIG pConfig;
|
||
|
|
||
|
DbgPrint((TRC_TRACK|TRC_STATE,"[StateIterateFn(0x%p)", pIntfContext));
|
||
|
DbgAssert((pIntfContext != NULL,"Invalid NULL context in {SIter} state"));
|
||
|
DbgAssert((pIntfContext->hIntf != INVALID_HANDLE_VALUE,"Invalid Ndisuio handle in {SIter} state"));
|
||
|
|
||
|
// Bump up the session handler for this intf context,
|
||
|
// since it starts plumbing new configs. No commands from older
|
||
|
// iterations should be accepted from now on.
|
||
|
pIntfContext->dwSessionHandle++;
|
||
|
|
||
|
// if either zero conf service is disabled for this context or there are no more configurations
|
||
|
// to try, transit to {SF} directly
|
||
|
if (!(pIntfContext->dwCtlFlags & INTFCTL_ENABLED) ||
|
||
|
pIntfContext->pwzcSList == NULL ||
|
||
|
pIntfContext->pwzcSList->NumberOfItems <= pIntfContext->pwzcSList->Index)
|
||
|
{
|
||
|
dwErr = ERROR_CONTINUE;
|
||
|
}
|
||
|
// check if the current config is marked "deleted". If this is the case, it means the {SRs} state
|
||
|
// exhausted all configurations. We should move to the failure state {SF}. The list of selected networks
|
||
|
// remains untouched - it is used in {SF} to update the blocked list. It is cleaned up in {SHr}.
|
||
|
else
|
||
|
{
|
||
|
pConfig = &(pIntfContext->pwzcSList->Config[pIntfContext->pwzcSList->Index]);
|
||
|
|
||
|
if (pConfig->dwCtlFlags & WZCCTL_INTERNAL_DELETED)
|
||
|
{
|
||
|
dwErr = ERROR_CONTINUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (dwErr == ERROR_SUCCESS)
|
||
|
{
|
||
|
DbgPrint((TRC_STATE,"Plumbing config %d", pIntfContext->pwzcSList->Index));
|
||
|
//Record current state into logging DB
|
||
|
DbLogWzcInfo(WZCSVC_SM_STATE_ITERATE, pIntfContext,
|
||
|
DbLogFmtSSID(0, &(pConfig->Ssid)), pConfig->InfrastructureMode);
|
||
|
// in this state we need to mark ncstatus = "connecting" again. We do it because we could
|
||
|
// come from a connected state, from {SRs} and {SPs}.
|
||
|
pIntfContext->ncStatus = NCS_CONNECTING;
|
||
|
// notify netman about the ncstatus change.
|
||
|
WzcNetmanNotify(pIntfContext);
|
||
|
|
||
|
// here we're about to plumb down a possibly new network. If it is indeed an SSID different from
|
||
|
// the one we have, we'll release the IP address (to force a Discover in the new net). Note that
|
||
|
// in {SIter}->{SRs}->{SIter} loops, no Release happens since the SSID should always coincide.
|
||
|
// Release might happen only on Hard resets when the SSID looks to be different from what is
|
||
|
// about to be plumbed down.
|
||
|
if (pConfig->Ssid.SsidLength != pIntfContext->wzcCurrent.Ssid.SsidLength ||
|
||
|
memcmp(pConfig->Ssid.Ssid, pIntfContext->wzcCurrent.Ssid.Ssid, pConfig->Ssid.SsidLength))
|
||
|
{
|
||
|
DbgPrint((TRC_STATE,"Requesting the release of the DHCP lease"));
|
||
|
// since Zero Configuration is the only one knowing that we are roaming to a new network
|
||
|
// it is up to Zero Configuration to trigger DHCP client lease refreshing. Otherwise DHCP
|
||
|
// client will act only on the Media Connect hence it will try to renew its old lease and
|
||
|
// being on the wrong network this will take a minute. Too long to wait.
|
||
|
DhcpReleaseParameters(pIntfContext->wszGuid);
|
||
|
}
|
||
|
else
|
||
|
DbgPrint((TRC_STATE,"Plumbing down the current SSID => skip the DHCP lease releasing"));
|
||
|
|
||
|
// we do have some entries in the selected configuration list (pwzcSList), and we
|
||
|
// do expect to have a valid index pointing in the list to the selected config
|
||
|
dwErr = LstSetSelectedConfig(pIntfContext, NULL);
|
||
|
|
||
|
if (dwErr == ERROR_SUCCESS)
|
||
|
{
|
||
|
// if everything went ok, we'll expect a media connect event in TMMS_Tp time.
|
||
|
// set this timer to fire up if the event doesn't come in.
|
||
|
TIMER_SET(pIntfContext, TMMS_Tp, dwErr);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
DbgPrint((TRC_STATE,"Remove the selected config since the driver failed setting it"));
|
||
|
// if anything went wrong on setting the selected config, don't bail out. Just remove
|
||
|
// this selected config and move to the next one
|
||
|
pIntfContext->dwCtlFlags |= INTFCTL_INTERNAL_FORCE_CFGREM;
|
||
|
pIntfContext->pfnStateHandler = StateCfgRemoveFn;
|
||
|
dwErr = ERROR_CONTINUE;
|
||
|
}
|
||
|
}
|
||
|
else // dwErr can't be anything else bug ERROR_CONTINUE in the 'else' branch
|
||
|
{
|
||
|
DbgPrint((TRC_STATE,"No configurations left in the selection list"));
|
||
|
//Record current state into logging DB
|
||
|
DbLogWzcInfo(WZCSVC_SM_STATE_ITERATE_NONET, pIntfContext);
|
||
|
pIntfContext->pfnStateHandler = StateFailedFn;
|
||
|
}
|
||
|
|
||
|
DbgPrint((TRC_TRACK|TRC_STATE,"StateIterateFn]=%d", dwErr));
|
||
|
return dwErr;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------
|
||
|
// StateConfiguredFn: Handler for the {SC} state
|
||
|
DWORD
|
||
|
StateConfiguredFn(
|
||
|
PINTF_CONTEXT pIntfContext)
|
||
|
{
|
||
|
DWORD dwErr = ERROR_SUCCESS;
|
||
|
|
||
|
DbgPrint((TRC_TRACK|TRC_STATE,"[StateConfiguredFn(0x%p)", pIntfContext));
|
||
|
DbgAssert((pIntfContext != NULL,"Invalid NULL context in {SQ} state"));
|
||
|
|
||
|
// since we're in {SC}, it means we cannot have a fake WEP Key, no matter what.
|
||
|
// hence, reset the INTFCTL_INTERNAL_FAKE_WKEY flag
|
||
|
pIntfContext->dwCtlFlags &= ~INTFCTL_INTERNAL_FAKE_WKEY;
|
||
|
|
||
|
// set the timer for the "configured" state life time
|
||
|
TIMER_SET(pIntfContext, TMMS_Tc, dwErr);
|
||
|
|
||
|
DbgPrint((TRC_TRACK|TRC_STATE,"StateConfiguredFn]=%d", dwErr));
|
||
|
return dwErr;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------
|
||
|
// StateFailedFn: Handler for the {SF} state
|
||
|
DWORD
|
||
|
StateFailedFn(
|
||
|
PINTF_CONTEXT pIntfContext)
|
||
|
{
|
||
|
DWORD dwErr = ERROR_SUCCESS;
|
||
|
|
||
|
DbgPrint((TRC_TRACK|TRC_STATE,"[StateFailedFn(0x%p)", pIntfContext));
|
||
|
DbgAssert((pIntfContext != NULL,"Invalid NULL context in {SF} state"));
|
||
|
// Record current state into logging DB
|
||
|
DbLogWzcInfo(WZCSVC_SM_STATE_FAILED, pIntfContext);
|
||
|
//
|
||
|
// a couple of things need to be done if the service is enabled and
|
||
|
// the driver supports all the needed OIDs
|
||
|
if (pIntfContext->dwCtlFlags & INTFCTL_OIDSSUPP &&
|
||
|
pIntfContext->dwCtlFlags & INTFCTL_ENABLED)
|
||
|
{
|
||
|
BYTE chSSID[32];
|
||
|
|
||
|
// send the failure notification down to TCP. This will cause TCP to
|
||
|
// generate the NetReady notification asap.
|
||
|
if (!(pIntfContext->dwCtlFlags & INTFCTL_INTERNAL_INITFAILNOTIF))
|
||
|
{
|
||
|
// call into the stack
|
||
|
DevioNotifyFailure(pIntfContext->wszGuid);
|
||
|
// make sure this call is never done twice for this adapter
|
||
|
pIntfContext->dwCtlFlags |= INTFCTL_INTERNAL_INITFAILNOTIF;
|
||
|
}
|
||
|
|
||
|
// whatever brought us here, the ncstatus should be "disconnected"
|
||
|
pIntfContext->ncStatus = NCS_MEDIA_DISCONNECTED;
|
||
|
// since the service is enabled, notify netman about the status change
|
||
|
WzcNetmanNotify(pIntfContext);
|
||
|
|
||
|
// this is when we should signal the failure if the signal is
|
||
|
// not to be suppressed and the service is enabled
|
||
|
if (pIntfContext->dwCtlFlags & INTFCTL_INTERNAL_SIGNAL)
|
||
|
{
|
||
|
WZCDLG_DATA dialogData = {0};
|
||
|
|
||
|
// once a signal has been generated, suppress further signals
|
||
|
// until passing through a successful configuration
|
||
|
pIntfContext->dwCtlFlags &= ~INTFCTL_INTERNAL_SIGNAL;
|
||
|
|
||
|
// bring up the "Failure" balloon
|
||
|
dialogData.dwCode = WZCDLG_FAILED;
|
||
|
dialogData.lParam = 0;
|
||
|
// count exactly how many visible configs we have (don't count
|
||
|
// SSIDs coming from APs that don't respond to broadcast SSID)
|
||
|
if (pIntfContext->pwzcVList != NULL)
|
||
|
{
|
||
|
UINT i;
|
||
|
|
||
|
for (i = 0; i < pIntfContext->pwzcVList->NumberOfItems; i++)
|
||
|
{
|
||
|
PNDIS_802_11_SSID pndSSID = &(pIntfContext->pwzcVList->Config[i].Ssid);
|
||
|
|
||
|
if (!WzcIsNullBuffer(pndSSID->Ssid, pndSSID->SsidLength))
|
||
|
dialogData.lParam++;
|
||
|
}
|
||
|
}
|
||
|
DbgPrint((TRC_STATE,"Generating balloon notification for %d visible networks", dialogData.lParam));
|
||
|
WzcDlgNotify(pIntfContext, &dialogData);
|
||
|
}
|
||
|
|
||
|
// just break the association here by plumbing down a hard coded SSID
|
||
|
// don't care about the return value.
|
||
|
ZeroMemory(&chSSID, sizeof(chSSID));
|
||
|
if (WzcRndGenBuffer(chSSID, 32, 1, 31) == ERROR_SUCCESS)
|
||
|
{
|
||
|
INTF_ENTRY IntfEntry;
|
||
|
|
||
|
ZeroMemory(&IntfEntry, sizeof(INTF_ENTRY));
|
||
|
IntfEntry.rdSSID.pData = chSSID;
|
||
|
IntfEntry.rdSSID.dwDataLen = 32;
|
||
|
IntfEntry.nInfraMode = Ndis802_11Infrastructure;
|
||
|
|
||
|
DevioSetIntfOIDs(
|
||
|
pIntfContext,
|
||
|
&IntfEntry,
|
||
|
INTF_SSID | INTF_INFRAMODE,
|
||
|
NULL);
|
||
|
|
||
|
// this is not an SSID we need to remember (being a random one)
|
||
|
ZeroMemory(&(pIntfContext->wzcCurrent.Ssid), sizeof(NDIS_802_11_SSID));
|
||
|
}
|
||
|
|
||
|
// regardless the above, update the "blocked" list
|
||
|
dwErr = LstUpdateBlockedList(pIntfContext);
|
||
|
DbgAssert((dwErr == ERROR_SUCCESS, "Failed with err %d updating the list of blocked configs!", dwErr));
|
||
|
}
|
||
|
|
||
|
|
||
|
// Bump up the session handler for this intf context,
|
||
|
// since it starts plumbing new configs. No commands from older
|
||
|
// iterations should be accepted from now on.
|
||
|
pIntfContext->dwSessionHandle++;
|
||
|
|
||
|
// If the card is courteous enough and talks to us, set a timer
|
||
|
// for 1 minute such that after this time we're getting an updated
|
||
|
// picture of what networks are around - at least that should be presented
|
||
|
// to the user.
|
||
|
if (pIntfContext->dwCtlFlags & INTFCTL_OIDSSUPP)
|
||
|
{
|
||
|
// Remain in {SF} one minute and scan again after that. It might look meaningless to do so
|
||
|
// in case the service is disabled, but keep in mind "disabled" means only "don't change
|
||
|
// anything on the card" (aside from the scan). That is, the view of what networks are
|
||
|
// available needs to be kept updated.
|
||
|
TIMER_SET(pIntfContext, TMMS_Tf, dwErr);
|
||
|
}
|
||
|
|
||
|
DbgPrint((TRC_TRACK|TRC_STATE,"StateFailedFn]=%d", dwErr));
|
||
|
return dwErr;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------
|
||
|
// StateCfgRemoveFn: Handler for the {SRs} state
|
||
|
DWORD
|
||
|
StateCfgRemoveFn(
|
||
|
PINTF_CONTEXT pIntfContext)
|
||
|
{
|
||
|
DWORD dwErr = ERROR_SUCCESS;
|
||
|
BOOL bConnectOK = FALSE;
|
||
|
PWZC_WLAN_CONFIG pConfig = &(pIntfContext->pwzcSList->Config[pIntfContext->pwzcSList->Index]);
|
||
|
|
||
|
DbgPrint((TRC_TRACK|TRC_STATE,"[StateCfgRemoveFn(0x%p)", pIntfContext));
|
||
|
DbgAssert((pIntfContext != NULL,"Invalid NULL context in {SRs} state"));
|
||
|
DbgAssert((pIntfContext->pwzcSList != NULL, "Invalid null selection list in {SRs} state"));
|
||
|
DbgAssert((pIntfContext->pwzcSList->Index < pIntfContext->pwzcSList->NumberOfItems, "Invalid selection index in {SRs} state"));
|
||
|
|
||
|
dwErr = DevioGetIntfStats(pIntfContext);
|
||
|
|
||
|
// debug print
|
||
|
DbgPrint((TRC_GENERIC, "Media State (%d) is %s",
|
||
|
pIntfContext->ulMediaState,
|
||
|
pIntfContext->ulMediaState == MEDIA_STATE_CONNECTED ? "Connected" : "Not Connected"));
|
||
|
|
||
|
// if we were explicitly requested to delete this configuration, do it so no matter what
|
||
|
if (pIntfContext->dwCtlFlags & INTFCTL_INTERNAL_FORCE_CFGREM)
|
||
|
{
|
||
|
DbgPrint((TRC_STATE,"The upper layer directs this config to be deleted => obey"));
|
||
|
bConnectOK = FALSE;
|
||
|
}
|
||
|
// .. but if the configuration we just plumbed is saying we need to consider this a success
|
||
|
// no matter what, do it so undoubtely
|
||
|
else if (pConfig->dwCtlFlags & WZCCTL_INTERNAL_FORCE_CONNECT)
|
||
|
{
|
||
|
DbgPrint((TRC_STATE,"This config requests forced connect => obey (transition to {SN})"));
|
||
|
bConnectOK = TRUE;
|
||
|
}
|
||
|
// if there is no special request and we were able to get the media status and we see the
|
||
|
// media being connected, this means the configuration is successful.
|
||
|
else if ((dwErr == ERROR_SUCCESS) && (pIntfContext->ulMediaState == MEDIA_STATE_CONNECTED))
|
||
|
{
|
||
|
DbgPrint((TRC_STATE,"Media is being connected in {SRs} =?=> transition to {SN}"));
|
||
|
bConnectOK = TRUE;
|
||
|
}
|
||
|
// in all other cases the configuration is going to be deleted
|
||
|
|
||
|
// go to the {SN} and {SC/SCk} based on the decision we took in bConnectOK
|
||
|
if (bConnectOK)
|
||
|
{
|
||
|
// ...assume the configuration was successful (although we didn't get
|
||
|
// the media connect event) and transition to {SN}
|
||
|
pIntfContext->pfnStateHandler = StateNotifyFn;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
UINT nFirstSelIdx; // first selection index following
|
||
|
UINT nNSelIdx; // new selection index to use
|
||
|
|
||
|
// if this is a forced delete, make sure we are not messing with old
|
||
|
// WZCCTL_INTERNAL_FORCE_CONNECT flags - they could cause such a configuration to
|
||
|
// be revived
|
||
|
if (pIntfContext->dwCtlFlags & INTFCTL_INTERNAL_FORCE_CFGREM)
|
||
|
{
|
||
|
pConfig->dwCtlFlags &= ~WZCCTL_INTERNAL_FORCE_CONNECT;
|
||
|
// since the upper layer is rejecting this configuration, make sure that
|
||
|
// future iterations won't try it again, This bit helps to build/update
|
||
|
// the BList (blocked list) when/if the state machine eventually gets
|
||
|
// into the failed state {SF}.
|
||
|
pConfig->dwCtlFlags |= WZCCTL_INTERNAL_BLOCKED;
|
||
|
}
|
||
|
|
||
|
// if this is an Adhoc network that just happened to fail, keep it in the list
|
||
|
// such that it will be retried one more time and when this happens it shall
|
||
|
// be considered successful no matter what.
|
||
|
// However this is to be done only if the upper layer is not asking explicitly for
|
||
|
// this config to be deleted
|
||
|
if (pConfig->InfrastructureMode == Ndis802_11IBSS &&
|
||
|
!(pConfig->dwCtlFlags & WZCCTL_INTERNAL_FORCE_CONNECT) &&
|
||
|
!(pIntfContext->dwCtlFlags & INTFCTL_INTERNAL_FORCE_CFGREM))
|
||
|
{
|
||
|
//Record current state into logging DB
|
||
|
DbLogWzcInfo(WZCSVC_SM_STATE_CFGSKIP, pIntfContext,
|
||
|
DbLogFmtSSID(0, &(pConfig->Ssid)));
|
||
|
|
||
|
pConfig->dwCtlFlags |= WZCCTL_INTERNAL_FORCE_CONNECT;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//Record current state into logging DB
|
||
|
DbLogWzcInfo(WZCSVC_SM_STATE_CFGREMOVE, pIntfContext,
|
||
|
DbLogFmtSSID(0, &(pConfig->Ssid)));
|
||
|
}
|
||
|
|
||
|
// mark this configuration as "bad"
|
||
|
pConfig->dwCtlFlags |= WZCCTL_INTERNAL_DELETED;
|
||
|
|
||
|
// regardless this was a forced or non-forced removal, reset the "force" control bit
|
||
|
pIntfContext->dwCtlFlags &= ~INTFCTL_INTERNAL_FORCE_CFGREM;
|
||
|
|
||
|
// if we just fail a "one time configuration", force a fresh beginning.
|
||
|
// and remove the "one time" flag as we're getting out of this mode
|
||
|
if (pIntfContext->dwCtlFlags & INTFCTL_INTERNAL_ONE_TIME)
|
||
|
{
|
||
|
DbgPrint((TRC_STATE,"Dropping a \"one time\" configuration"));
|
||
|
pIntfContext->pwzcSList->Index = (pIntfContext->pwzcSList->NumberOfItems - 1);
|
||
|
pIntfContext->dwCtlFlags &= ~INTFCTL_INTERNAL_ONE_TIME;
|
||
|
}
|
||
|
|
||
|
// scan for the next configuration that has not been marked "bad" yet
|
||
|
for (nNSelIdx = (pIntfContext->pwzcSList->Index + 1) % pIntfContext->pwzcSList->NumberOfItems;
|
||
|
nNSelIdx != pIntfContext->pwzcSList->Index;
|
||
|
nNSelIdx = (nNSelIdx + 1) % pIntfContext->pwzcSList->NumberOfItems)
|
||
|
{
|
||
|
pConfig = &(pIntfContext->pwzcSList->Config[nNSelIdx]);
|
||
|
if (!(pConfig->dwCtlFlags & WZCCTL_INTERNAL_DELETED))
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// if we couldn't find any better candidate ...
|
||
|
if (pConfig->dwCtlFlags & WZCCTL_INTERNAL_DELETED)
|
||
|
{
|
||
|
BOOL bFoundCandidate = FALSE;
|
||
|
|
||
|
DbgPrint((TRC_STATE,"Went through all configs. Reviving now failed Adhocs."));
|
||
|
|
||
|
// revive the adhocs that failed previously
|
||
|
// This means that we reset the WZCCTL_INTERNAL_DELETED flag from all the configurations
|
||
|
// having the WZCCTL_INTERNAL_FORCE_CONNECT flag set, and we let the latter untouched.
|
||
|
// Because of this flag we'll actually consider the configuration to be successful when
|
||
|
// it will be plumbed again. From that point on, it will be only the upper layer who will
|
||
|
// be able to delete it again, and it is then when the WZCCTL_INTERNAL_FORCE_CONNECT
|
||
|
// gets reset.
|
||
|
for (nNSelIdx = 0; nNSelIdx < pIntfContext->pwzcSList->NumberOfItems; nNSelIdx++)
|
||
|
{
|
||
|
pConfig = &(pIntfContext->pwzcSList->Config[nNSelIdx]);
|
||
|
if (pConfig->dwCtlFlags & WZCCTL_INTERNAL_FORCE_CONNECT)
|
||
|
{
|
||
|
DbgPrint((TRC_STATE,"Reviving configuration %d.", nNSelIdx));
|
||
|
|
||
|
pConfig->dwCtlFlags &= ~WZCCTL_INTERNAL_DELETED;
|
||
|
// the first configuration in this position is the candidate we were looking for
|
||
|
if (!bFoundCandidate)
|
||
|
{
|
||
|
pIntfContext->pwzcSList->Index = nNSelIdx;
|
||
|
bFoundCandidate = TRUE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// if !bFoundCandidate, the configuration currently pointed by the pwzcSList->Index has
|
||
|
// the "deleted" bit on. This will make {SIter} to jump directly to {SF}.
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// if we could find another candidate, set the index to point it
|
||
|
pIntfContext->pwzcSList->Index = nNSelIdx;
|
||
|
}
|
||
|
|
||
|
// transition automatically to {SIter} state
|
||
|
pIntfContext->pfnStateHandler = StateIterateFn;
|
||
|
}
|
||
|
|
||
|
dwErr = ERROR_CONTINUE;
|
||
|
|
||
|
DbgPrint((TRC_TRACK|TRC_STATE,"StateCfgRemoveFn]=%d", dwErr));
|
||
|
return dwErr;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------
|
||
|
// StateCfgPreserveFn: Handler for the {SPs} state
|
||
|
DWORD
|
||
|
StateCfgPreserveFn(
|
||
|
PINTF_CONTEXT pIntfContext)
|
||
|
{
|
||
|
DWORD dwErr = ERROR_SUCCESS;
|
||
|
PWZC_WLAN_CONFIG pConfig;
|
||
|
UINT nNSelIdx;
|
||
|
UINT i;
|
||
|
|
||
|
DbgPrint((TRC_TRACK|TRC_STATE,"[StateCfgPreserveFn(0x%p)", pIntfContext));
|
||
|
DbgAssert((pIntfContext != NULL,"Invalid NULL context in {SPs} state"));
|
||
|
DbgAssert((pIntfContext->pwzcSList != NULL, "Invalid null selection list in {SPs} state"));
|
||
|
DbgAssert((pIntfContext->pwzcSList->Index < pIntfContext->pwzcSList->NumberOfItems, "Invalid selection index in {SPs} state"));
|
||
|
|
||
|
pConfig = &(pIntfContext->pwzcSList->Config[pIntfContext->pwzcSList->Index]);
|
||
|
//Record current state into logging DB
|
||
|
DbLogWzcInfo(WZCSVC_SM_STATE_CFGPRESERVE, pIntfContext,
|
||
|
DbLogFmtSSID(0, &(pConfig->Ssid)));
|
||
|
|
||
|
// if we just skip a "one time configuration", then don't move the pointer out of it.
|
||
|
// Basically we'll retry the same configuration over and over again until (if it completly
|
||
|
// fails) it is removed from the selection list by the upper layer (802.1x).
|
||
|
if (!(pIntfContext->dwCtlFlags & INTFCTL_INTERNAL_ONE_TIME))
|
||
|
{
|
||
|
// scan for the next configuration which has not been marked "bad" yet
|
||
|
for (i = 0, nNSelIdx = (pIntfContext->pwzcSList->Index + 1) % pIntfContext->pwzcSList->NumberOfItems;
|
||
|
i < pIntfContext->pwzcSList->NumberOfItems;
|
||
|
i++, nNSelIdx = (nNSelIdx + 1) % pIntfContext->pwzcSList->NumberOfItems)
|
||
|
{
|
||
|
pConfig = &(pIntfContext->pwzcSList->Config[nNSelIdx]);
|
||
|
if (!(pConfig->dwCtlFlags & WZCCTL_INTERNAL_DELETED))
|
||
|
break;
|
||
|
}
|
||
|
// if we couldn't find any, clear the selection list and go back to
|
||
|
// {SIter}. It will transition consequently to {SF}
|
||
|
if (i == pIntfContext->pwzcSList->NumberOfItems)
|
||
|
{
|
||
|
WzcCleanupWzcList(pIntfContext->pwzcSList);
|
||
|
pIntfContext->pwzcSList = NULL;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// if we could find another candidate, set the index to point it
|
||
|
pIntfContext->pwzcSList->Index = nNSelIdx;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pIntfContext->pfnStateHandler = StateIterateFn;
|
||
|
dwErr = ERROR_CONTINUE;
|
||
|
|
||
|
DbgPrint((TRC_TRACK|TRC_STATE,"StateCfgPreserveFn]=%d", dwErr));
|
||
|
return dwErr;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------
|
||
|
// StateCfgHardKeyFn: Handler for the {SCk} state
|
||
|
DWORD
|
||
|
StateCfgHardKeyFn(
|
||
|
PINTF_CONTEXT pIntfContext)
|
||
|
{
|
||
|
DWORD dwErr = ERROR_SUCCESS;
|
||
|
PWZC_WLAN_CONFIG pSConfig;
|
||
|
|
||
|
DbgPrint((TRC_TRACK|TRC_STATE,"[StateCfgHardKeyFn(0x%p)", pIntfContext));
|
||
|
DbgAssert((pIntfContext != NULL,"Invalid NULL context in {SCk} state"));
|
||
|
|
||
|
// get a pointer to the currently selected configuration
|
||
|
pSConfig = &(pIntfContext->pwzcSList->Config[pIntfContext->pwzcSList->Index]);
|
||
|
|
||
|
//Record current state into logging DB
|
||
|
DbLogWzcInfo(WZCSVC_SM_STATE_CFGHDKEY, pIntfContext,
|
||
|
DbLogFmtSSID(0, &(pSConfig->Ssid)));
|
||
|
|
||
|
TIMER_SET(pIntfContext, TMMS_Tc, dwErr);
|
||
|
|
||
|
DbgPrint((TRC_TRACK|TRC_STATE,"StateCfgHardKeyFn]=%d", dwErr));
|
||
|
return dwErr;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------
|
||
|
// StateNotifyFn: Handler for the {SN} state
|
||
|
DWORD
|
||
|
StateNotifyFn(
|
||
|
PINTF_CONTEXT pIntfContext)
|
||
|
{
|
||
|
DWORD dwErr = ERROR_SUCCESS;
|
||
|
PWZC_WLAN_CONFIG pSConfig;
|
||
|
PWZC_DEVICE_NOTIF pwzcNotif;
|
||
|
DWORD i;
|
||
|
|
||
|
DbgPrint((TRC_TRACK|TRC_STATE,"[StateNotifyFn(0x%p)", pIntfContext));
|
||
|
DbgAssert((pIntfContext != NULL,"Invalid NULL context in {SN} state"));
|
||
|
DbgAssert((pIntfContext->pwzcSList != NULL, "Invalid null selection list in {SN} state"));
|
||
|
DbgAssert((pIntfContext->pwzcSList->Index < pIntfContext->pwzcSList->NumberOfItems, "Invalid selection index in {SN} state"));
|
||
|
|
||
|
// we have a valid 802.11 config, so the ncstatus should read "connected"
|
||
|
pIntfContext->ncStatus = NCS_CONNECTED;
|
||
|
// notify netman about the ncstatus change (no need to check whether the
|
||
|
// service is enabled or not - it is enabled, otherwise we won't be in this state)
|
||
|
WzcNetmanNotify(pIntfContext);
|
||
|
|
||
|
// get a pointer to the currently selected configuration
|
||
|
pSConfig = &(pIntfContext->pwzcSList->Config[pIntfContext->pwzcSList->Index]);
|
||
|
|
||
|
// get the BSSID to which we're associated.
|
||
|
// if the BSSID was successfully retrieved then use this to generate the initial
|
||
|
// dynamic session keys. LstGenInitialSessionKeys is successfull only if the current
|
||
|
// configuration allows (association is successful and there is a user-provided wep key)
|
||
|
dwErr = DevioRefreshIntfOIDs(pIntfContext, INTF_BSSID, NULL);
|
||
|
|
||
|
//Record current state into logging DB as the first thing.
|
||
|
DbLogWzcInfo(WZCSVC_SM_STATE_NOTIFY, pIntfContext,
|
||
|
DbLogFmtSSID(0, &(pSConfig->Ssid)),
|
||
|
DbLogFmtBSSID(1, pIntfContext->wzcCurrent.MacAddress));
|
||
|
|
||
|
// now check if there was any error getting the BSSID - if it was, log it.
|
||
|
// otherwise go on and generate the initial session keys.
|
||
|
if (dwErr != ERROR_SUCCESS)
|
||
|
{
|
||
|
// if there was any error getting the BSSID, log the error here
|
||
|
DbLogWzcError(WZCSVC_ERR_QUERRY_BSSID, pIntfContext, dwErr);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
dwErr = LstGenInitialSessionKeys(pIntfContext);
|
||
|
// if there was any error setting the initial session keys, log it here
|
||
|
if (dwErr != ERROR_SUCCESS)
|
||
|
DbLogWzcError(WZCSVC_ERR_GEN_SESSION_KEYS, pIntfContext, dwErr);
|
||
|
}
|
||
|
// no error is critical enough so far to justify stopping the state machine.
|
||
|
// .. so reset it to "success"
|
||
|
dwErr = ERROR_SUCCESS;
|
||
|
|
||
|
// allocate memory for a WZC_CONFIG_NOTIF object large enough to include the interface's GUID
|
||
|
pwzcNotif = MemCAlloc(sizeof(WZC_DEVICE_NOTIF) + wcslen(pIntfContext->wszGuid)*sizeof(WCHAR));
|
||
|
if (pwzcNotif == NULL)
|
||
|
{
|
||
|
DbgAssert((FALSE, "Out of memory on allocating the WZC_DEVICE_NOTIF object"));
|
||
|
dwErr = GetLastError();
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
// initialize the WZC_CONFIG_NOTIF
|
||
|
// this is a WZCNOTIF_WZC_CONNECT event that is going up
|
||
|
pwzcNotif->dwEventType = WZCNOTIF_WZC_CONNECT;
|
||
|
pwzcNotif->wzcConfig.dwSessionHdl = pIntfContext->dwSessionHandle;
|
||
|
wcscpy(pwzcNotif->wzcConfig.wszGuid, pIntfContext->wszGuid);
|
||
|
memcpy(&pwzcNotif->wzcConfig.ndSSID, &pSConfig->Ssid, sizeof(NDIS_802_11_SSID));
|
||
|
// copy into the notification the user data associated with the current config
|
||
|
pwzcNotif->wzcConfig.rdEventData.dwDataLen = pSConfig->rdUserData.dwDataLen;
|
||
|
if (pwzcNotif->wzcConfig.rdEventData.dwDataLen > 0)
|
||
|
{
|
||
|
pwzcNotif->wzcConfig.rdEventData.pData = MemCAlloc(pSConfig->rdUserData.dwDataLen);
|
||
|
if (pwzcNotif->wzcConfig.rdEventData.pData == NULL)
|
||
|
{
|
||
|
DbgAssert((FALSE, "Out of memory on allocating the WZC_CONFIG_NOTIF user data"));
|
||
|
dwErr = GetLastError();
|
||
|
MemFree(pwzcNotif);
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
memcpy(pwzcNotif->wzcConfig.rdEventData.pData,
|
||
|
pSConfig->rdUserData.pData,
|
||
|
pSConfig->rdUserData.dwDataLen);
|
||
|
}
|
||
|
|
||
|
// Asynchronously call into the upper level app (802.1x)
|
||
|
// notifying that the selected 802.11 configuration is successful.
|
||
|
DbgPrint((TRC_NOTIF, "Sending WZCNOTIF_WZC_CONNECT notification (SessHdl %d)",
|
||
|
pIntfContext->dwSessionHandle));
|
||
|
|
||
|
InterlockedIncrement(&g_nThreads);
|
||
|
if (!QueueUserWorkItem((LPTHREAD_START_ROUTINE)WZCWrkWzcSendNotif,
|
||
|
(LPVOID)pwzcNotif,
|
||
|
WT_EXECUTELONGFUNCTION))
|
||
|
{
|
||
|
DbgAssert((FALSE, "Can't create WZCWrkWzcSendNotif worker thread"));
|
||
|
dwErr = GetLastError();
|
||
|
InterlockedDecrement(&g_nThreads);
|
||
|
MemFree(pwzcNotif->wzcConfig.rdEventData.pData);
|
||
|
MemFree(pwzcNotif);
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
DbgPrint((TRC_STATE,"Requesting the refresh of the DHCP lease"));
|
||
|
// once the configuration has been set up correctly, Zero Conf needs to trigger
|
||
|
// DHCP client one more time asking for the lease to be refreshed. It needs to do so
|
||
|
// because it is not guaranteed that a media connect notification will be generated
|
||
|
// and hence DHCP client might have no knowledge about the network being brought up
|
||
|
// back. Note also the call below is (and should be) asynchronous
|
||
|
DhcpStaticRefreshParams(pIntfContext->wszGuid);
|
||
|
|
||
|
// at this point, set back the "signal" control bit since right now we're in the
|
||
|
// successful case! On next failure (whenever that might be) the signal must not
|
||
|
// be suppressed.
|
||
|
pIntfContext->dwCtlFlags |= INTFCTL_INTERNAL_SIGNAL;
|
||
|
|
||
|
// also, mark this context has been sent up the notification to 802.1x.
|
||
|
// If we're here because of a PnP event, then this will tell to the notification
|
||
|
// handler to not forward the notification up since it would be totally redundant.
|
||
|
// If this is not PnP event, this bit will be cleaned up by whoever called StateDispatchEvent.
|
||
|
pIntfContext->dwCtlFlags |= INTFCTL_INTERNAL_BLK_MEDIACONN;
|
||
|
|
||
|
// automatic transition to either {SCk} or {SC} depending whether the remote guy
|
||
|
// is requiring privacy and we rely the privacy on a faked key
|
||
|
if (pSConfig->Privacy && pIntfContext->dwCtlFlags & INTFCTL_INTERNAL_FAKE_WKEY)
|
||
|
pIntfContext->pfnStateHandler = StateCfgHardKeyFn;
|
||
|
else
|
||
|
pIntfContext->pfnStateHandler = StateConfiguredFn;
|
||
|
dwErr = ERROR_CONTINUE;
|
||
|
|
||
|
exit:
|
||
|
DbgPrint((TRC_TRACK|TRC_STATE,"StateNotifyFn]=%d", dwErr));
|
||
|
return dwErr;
|
||
|
}
|