/* Copyright (c) 2003 Juan Lang
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */
#include "config.h"
#include "wine/debug.h"
#include "nbcmdqueue.h"
#include "netbios.h"

WINE_DEFAULT_DEBUG_CHANNEL(netbios);

/* This file provides a NetBIOS emulator that implements the NetBIOS interface,
 * including thread safety and asynchronous call support.  The protocol
 * implementation is separate, with blocking (synchronous) functions.
 */

#define ADAPTERS_INCR 8
#define DEFAULT_NUM_SESSIONS 16

typedef struct _NetBIOSTransportTableEntry
{
    ULONG            id;
    NetBIOSTransport transport;
} NetBIOSTransportTableEntry;

typedef struct _NetBIOSSession
{
    BOOL  inUse;
    UCHAR state;
    UCHAR local_name[NCBNAMSZ];
    UCHAR remote_name[NCBNAMSZ];
    void *data;
} NetBIOSSession;

/* This struct needs a little explanation, unfortunately.  enabled is only
 * used by nbInternalEnum (see).  If transport_id is not 0 and transport
 * is not NULL, the adapter is considered valid.  (transport is a pointer to
 * an entry in a NetBIOSTransportTableEntry.)  data has data for the callers of
 * NetBIOSEnumAdapters to be able to see.  The lana is repeated there, even
 * though I don't use it internally--it's for transports to use reenabling
 * adapters using NetBIOSEnableAdapter.
 */
typedef struct _NetBIOSAdapter
{
    BOOL               enabled;
    BOOL               shuttingDown;
    LONG               resetting;
    ULONG              transport_id;
    NetBIOSTransport  *transport;
    NetBIOSAdapterImpl impl;
    struct NBCmdQueue *cmdQueue;
    CRITICAL_SECTION   cs;
    DWORD              sessionsLen;
    NetBIOSSession    *sessions;
} NetBIOSAdapter;

typedef struct _NetBIOSAdapterTable {
    CRITICAL_SECTION cs;
    BOOL             enumerated;
    BOOL             enumerating;
    UCHAR            tableSize;
    NetBIOSAdapter  *table;
} NetBIOSAdapterTable;

/* Just enough space for NBT right now */
static NetBIOSTransportTableEntry gTransports[1];
static UCHAR gNumTransports = 0;
static NetBIOSAdapterTable gNBTable;

static UCHAR nbResizeAdapterTable(UCHAR newSize)
{
    UCHAR ret;

    if (gNBTable.table)
        gNBTable.table = HeapReAlloc(GetProcessHeap(),
         HEAP_ZERO_MEMORY, gNBTable.table,
         newSize * sizeof(NetBIOSAdapter));
    else
        gNBTable.table = HeapAlloc(GetProcessHeap(),
         HEAP_ZERO_MEMORY, newSize * sizeof(NetBIOSAdapter));
    if (gNBTable.table)
    {
        gNBTable.tableSize = newSize;
        ret = NRC_GOODRET;
    }
    else
        ret = NRC_OSRESNOTAV;
    return ret;
}

void NetBIOSInit(void)
{
    memset(&gNBTable, 0, sizeof(gNBTable));
    InitializeCriticalSection(&gNBTable.cs);
    gNBTable.cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": NetBIOSAdapterTable.cs");
}

void NetBIOSShutdown(void)
{
    UCHAR i;

    EnterCriticalSection(&gNBTable.cs);
    for (i = 0; i < gNBTable.tableSize; i++)
    {
        if (gNBTable.table[i].transport &&
         gNBTable.table[i].transport->cleanupAdapter)
            gNBTable.table[i].transport->cleanupAdapter(
             gNBTable.table[i].impl.data);
    }
    for (i = 0; i < gNumTransports; i++)
        if (gTransports[i].transport.cleanup)
            gTransports[i].transport.cleanup();
    LeaveCriticalSection(&gNBTable.cs);
    gNBTable.cs.DebugInfo->Spare[0] = 0;
    DeleteCriticalSection(&gNBTable.cs);
    HeapFree(GetProcessHeap(), 0, gNBTable.table);
}

BOOL NetBIOSRegisterTransport(ULONG id, NetBIOSTransport *transport)
{
    BOOL ret;

    TRACE(": transport 0x%08x, p %p\n", id, transport);
    if (!transport)
        ret = FALSE;
    else if (gNumTransports >= sizeof(gTransports) / sizeof(gTransports[0]))
    {
        FIXME("Too many transports %d\n", gNumTransports + 1);
        ret = FALSE;
    }
    else
    {
        UCHAR i;

        ret = FALSE;
        for (i = 0; !ret && i < gNumTransports; i++)
        {
            if (gTransports[i].id == id)
            {
                WARN("Replacing NetBIOS transport ID %d\n", id);
                memcpy(&gTransports[i].transport, transport,
                 sizeof(NetBIOSTransport));
                ret = TRUE;
            }
        }
        if (!ret)
        {
            gTransports[gNumTransports].id = id;
            memcpy(&gTransports[gNumTransports].transport, transport,
             sizeof(NetBIOSTransport));
            gNumTransports++;
            ret = TRUE;
        }
    }
    TRACE("returning %d\n", ret);
    return ret;
}

/* In this, I acquire the table lock to make sure no one else is modifying it.
 * This is _probably_ overkill since it should only be called during the
 * context of a NetBIOSEnum call, but just to be safe..
 */
BOOL NetBIOSRegisterAdapter(ULONG transport, DWORD ifIndex, void *data)
{
    BOOL ret;
    UCHAR i;

    TRACE(": transport 0x%08x, ifIndex 0x%08x, data %p\n", transport, ifIndex,
     data);
    for (i = 0; i < gNumTransports && gTransports[i].id != transport; i++)
        ;
    if ((i < gNumTransports) && gTransports[i].id == transport)
    {
        NetBIOSTransport *transportPtr = &gTransports[i].transport;

        TRACE(": found transport %p for id 0x%08x\n", transportPtr, transport);

        EnterCriticalSection(&gNBTable.cs);
        ret = FALSE;
        for (i = 0; i < gNBTable.tableSize &&
         gNBTable.table[i].transport != 0; i++)
            ;
        if (i == gNBTable.tableSize && gNBTable.tableSize < MAX_LANA + 1)
        {
            UCHAR newSize;

            if (gNBTable.tableSize < (MAX_LANA + 1) - ADAPTERS_INCR)
                newSize = gNBTable.tableSize + ADAPTERS_INCR;
            else
                newSize = MAX_LANA + 1;
            nbResizeAdapterTable(newSize);
        }
        if (i < gNBTable.tableSize && gNBTable.table[i].transport == 0)
        {
            TRACE(": registering as LANA %d\n", i);
            gNBTable.table[i].transport_id = transport;
            gNBTable.table[i].transport = transportPtr;
            gNBTable.table[i].impl.lana = i;
            gNBTable.table[i].impl.ifIndex = ifIndex;
            gNBTable.table[i].impl.data = data;
            gNBTable.table[i].cmdQueue = NBCmdQueueCreate(GetProcessHeap());
            InitializeCriticalSection(&gNBTable.table[i].cs);
            gNBTable.table[i].cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": NetBIOSAdapterTable.NetBIOSAdapter.cs");
            gNBTable.table[i].enabled = TRUE;
            ret = TRUE;
        }
        LeaveCriticalSection(&gNBTable.cs);
    }
    else
        ret = FALSE;
    TRACE("returning %d\n", ret);
    return ret;
}

/* In this, I acquire the table lock to make sure no one else is modifying it.
 * This is _probably_ overkill since it should only be called during the
 * context of a NetBIOSEnum call, but just to be safe..
 */
void NetBIOSEnableAdapter(UCHAR lana)
{
    TRACE(": %d\n", lana);
    if (lana < gNBTable.tableSize)
    {
        EnterCriticalSection(&gNBTable.cs);
        if (gNBTable.table[lana].transport != 0)
            gNBTable.table[lana].enabled = TRUE;
        LeaveCriticalSection(&gNBTable.cs);
    }
}

static void nbShutdownAdapter(NetBIOSAdapter *adapter)
{
    if (adapter)
    {
        adapter->shuttingDown = TRUE;
        NBCmdQueueCancelAll(adapter->cmdQueue);
        if (adapter->transport->cleanupAdapter)
            adapter->transport->cleanupAdapter(adapter->impl.data);
        NBCmdQueueDestroy(adapter->cmdQueue);
        adapter->cs.DebugInfo->Spare[0] = 0;
        DeleteCriticalSection(&adapter->cs);
        memset(adapter, 0, sizeof(NetBIOSAdapter));
    }
}

static void nbInternalEnum(void)
{
    UCHAR i;

    EnterCriticalSection(&gNBTable.cs);
    TRACE("before mark\n");
    /* mark: */
    for (i = 0; i < gNBTable.tableSize; i++)
        if (gNBTable.table[i].enabled && gNBTable.table[i].transport != 0)
            gNBTable.table[i].enabled = FALSE;

    TRACE("marked, before store, %d transports\n", gNumTransports);
    /* store adapters: */
    for (i = 0; i < gNumTransports; i++)
        if (gTransports[i].transport.enumerate)
            gTransports[i].transport.enumerate();

    TRACE("before sweep\n");
    /* sweep: */
    for (i = 0; i < gNBTable.tableSize; i++)
        if (!gNBTable.table[i].enabled && gNBTable.table[i].transport != 0)
            nbShutdownAdapter(&gNBTable.table[i]);
    gNBTable.enumerated = TRUE;
    LeaveCriticalSection(&gNBTable.cs);
}

UCHAR NetBIOSNumAdapters(void)
{
    UCHAR ret, i;

    if (!gNBTable.enumerated)
        nbInternalEnum();
    for (i = 0, ret = 0; i < gNBTable.tableSize; i++)
        if (gNBTable.table[i].transport != 0)
            ret++;
    return ret;
}

void NetBIOSEnumAdapters(ULONG transport, NetBIOSEnumAdaptersCallback cb,
 void *closure)
{
    TRACE("transport 0x%08x, callback %p, closure %p\n", transport, cb,
     closure);
    if (cb)
    {
        BOOL enumAll = memcmp(&transport, ALL_TRANSPORTS, sizeof(ULONG)) == 0;
        UCHAR i, numLANAs = 0;

        EnterCriticalSection(&gNBTable.cs);
        if (!gNBTable.enumerating)
        {
            gNBTable.enumerating = TRUE;
            nbInternalEnum();
            gNBTable.enumerating = FALSE;
        }
        for (i = 0; i < gNBTable.tableSize; i++)
            if (enumAll || gNBTable.table[i].transport_id == transport)
                numLANAs++;
        if (numLANAs > 0)
        {
            UCHAR lanaIndex = 0;

            for (i = 0; i < gNBTable.tableSize; i++)
                if (gNBTable.table[i].transport_id != 0 &&
                 (enumAll || gNBTable.table[i].transport_id == transport))
                    cb(numLANAs, lanaIndex++, gNBTable.table[i].transport_id,
                     &gNBTable.table[i].impl, closure);
        }
        LeaveCriticalSection(&gNBTable.cs);
    }
}

static NetBIOSAdapter *nbGetAdapter(UCHAR lana)
{
    NetBIOSAdapter *ret = NULL;

    TRACE(": lana %d, num allocated adapters %d\n", lana, gNBTable.tableSize);
    if (lana < gNBTable.tableSize && gNBTable.table[lana].transport_id != 0
     && gNBTable.table[lana].transport)
        ret = &gNBTable.table[lana];
    TRACE("returning %p\n", ret);
    return ret;
}

static UCHAR nbEnum(PNCB ncb)
{
    PLANA_ENUM lanas = (PLANA_ENUM)ncb->ncb_buffer;
    UCHAR i, ret;

    TRACE(": ncb %p\n", ncb);

    if (!lanas)
        ret = NRC_BUFLEN;
    else if (ncb->ncb_length < sizeof(LANA_ENUM))
        ret = NRC_BUFLEN;
    else
    {
        nbInternalEnum();
        lanas->length = 0;
        for (i = 0; i < gNBTable.tableSize; i++)
            if (gNBTable.table[i].transport)
            {
                lanas->length++;
                lanas->lana[i] = i;
            }
        ret = NRC_GOODRET;
    }
    TRACE("returning 0x%02x\n", ret);
    return ret;
}

static UCHAR nbInternalHangup(NetBIOSAdapter *adapter, NetBIOSSession *session);

static UCHAR nbCancel(NetBIOSAdapter *adapter, PNCB ncb)
{
    UCHAR ret;

    TRACE(": adapter %p, ncb %p\n", adapter, ncb);

    if (!adapter) return NRC_BRIDGE;
    if (!ncb) return NRC_INVADDRESS;

    switch (ncb->ncb_command & 0x7f)
    {
        case NCBCANCEL:
        case NCBADDNAME:
        case NCBADDGRNAME:
        case NCBDELNAME:
        case NCBRESET:
        case NCBSSTAT:
            ret = NRC_CANCEL;
            break;

        /* NCBCALL, NCBCHAINSEND/NCBSEND, NCBHANGUP all close the associated
         * session if cancelled */
        case NCBCALL:
        case NCBSEND:
        case NCBCHAINSEND:
        case NCBSENDNA:
        case NCBCHAINSENDNA:
        case NCBHANGUP:
        {
            if (ncb->ncb_lsn >= adapter->sessionsLen)
                ret = NRC_SNUMOUT;
            else if (!adapter->sessions[ncb->ncb_lsn].inUse)
                ret = NRC_SNUMOUT;
            else
            {
                ret = NBCmdQueueCancel(adapter->cmdQueue, ncb);
                if (ret == NRC_CMDCAN || ret == NRC_CANOCCR)
                    nbInternalHangup(adapter, &adapter->sessions[ncb->ncb_lsn]);
            }
            break;
        }

        default:
            ret = NBCmdQueueCancel(adapter->cmdQueue, ncb);
    }
    TRACE("returning 0x%02x\n", ret);
    return ret;
}

/* Resizes adapter to contain space for at least sessionsLen sessions.
 * If allocating more space for sessions, sets the adapter's sessionsLen to
 * sessionsLen.  If the adapter's sessionsLen was already at least sessionsLen,
 * does nothing.  Does not modify existing sessions.  Assumes the adapter is
 * locked.
 * Returns NRC_GOODRET on success, and something else on failure.
 */
static UCHAR nbResizeAdapter(NetBIOSAdapter *adapter, UCHAR sessionsLen)
{
    UCHAR ret = NRC_GOODRET;

    if (adapter && adapter->sessionsLen < sessionsLen)
    {
        NetBIOSSession *newSessions;

        if (adapter->sessions)
            newSessions = HeapReAlloc(GetProcessHeap(),
             HEAP_ZERO_MEMORY, adapter->sessions, sessionsLen *
             sizeof(NetBIOSSession));
        else
            newSessions = HeapAlloc(GetProcessHeap(),
             HEAP_ZERO_MEMORY, sessionsLen * sizeof(NetBIOSSession));
        if (newSessions)
        {
            adapter->sessions = newSessions;
            adapter->sessionsLen = sessionsLen;
        }
        else
            ret = NRC_OSRESNOTAV;
    }
    return ret;
}

static UCHAR nbReset(NetBIOSAdapter *adapter, PNCB ncb)
{
    UCHAR ret;

    TRACE(": adapter %p, ncb %p\n", adapter, ncb);

    if (!adapter) return NRC_BRIDGE;
    if (!ncb) return NRC_INVADDRESS;

    if (InterlockedIncrement(&adapter->resetting) == 1)
    {
        UCHAR i, resizeTo;

        NBCmdQueueCancelAll(adapter->cmdQueue);

        EnterCriticalSection(&adapter->cs);
        for (i = 0; i < adapter->sessionsLen; i++)
            if (adapter->sessions[i].inUse)
                nbInternalHangup(adapter, &adapter->sessions[i]);
        if (!ncb->ncb_lsn)
            resizeTo = ncb->ncb_callname[0] == 0 ? DEFAULT_NUM_SESSIONS :
             ncb->ncb_callname[0];
        else if (adapter->sessionsLen == 0)
            resizeTo = DEFAULT_NUM_SESSIONS;
        else
            resizeTo = 0;
        if (resizeTo > 0)
            ret = nbResizeAdapter(adapter, resizeTo);
        else
            ret = NRC_GOODRET;
        LeaveCriticalSection(&adapter->cs);
    }
    else
        ret = NRC_TOOMANY;
    InterlockedDecrement(&adapter->resetting);
    TRACE("returning 0x%02x\n", ret);
    return ret;
}

static UCHAR nbSStat(NetBIOSAdapter *adapter, PNCB ncb)
{
    UCHAR ret, i, spaceFor;
    PSESSION_HEADER sstat;

    TRACE(": adapter %p, NCB %p\n", adapter, ncb);

    if (!adapter) return NRC_BADDR;
    if (adapter->sessionsLen == 0) return NRC_ENVNOTDEF;
    if (!ncb) return NRC_INVADDRESS;
    if (!ncb->ncb_buffer) return NRC_BADDR;
    if (ncb->ncb_length < sizeof(SESSION_HEADER)) return NRC_BUFLEN;

    sstat = (PSESSION_HEADER)ncb->ncb_buffer;
    ret = NRC_GOODRET;
    memset(sstat, 0, sizeof(SESSION_HEADER));
    spaceFor = (ncb->ncb_length - sizeof(SESSION_HEADER)) /
     sizeof(SESSION_BUFFER);
    EnterCriticalSection(&adapter->cs);
    for (i = 0; ret == NRC_GOODRET && i < adapter->sessionsLen; i++)
    {
        if (adapter->sessions[i].inUse && (ncb->ncb_name[0] == '*' ||
         !memcmp(ncb->ncb_name, adapter->sessions[i].local_name, NCBNAMSZ)))
        {
            if (sstat->num_sess < spaceFor)
            {
                PSESSION_BUFFER buf;
               
                buf = (PSESSION_BUFFER)((PUCHAR)sstat + sizeof(SESSION_HEADER)
                 + sstat->num_sess * sizeof(SESSION_BUFFER));
                buf->lsn = i;
                buf->state = adapter->sessions[i].state;
                memcpy(buf->local_name, adapter->sessions[i].local_name,
                 NCBNAMSZ);
                memcpy(buf->remote_name, adapter->sessions[i].remote_name,
                 NCBNAMSZ);
                buf->rcvs_outstanding = buf->sends_outstanding = 0;
                sstat->num_sess++;
            }
            else
                ret = NRC_BUFLEN;
        }
    }
    LeaveCriticalSection(&adapter->cs);

    TRACE("returning 0x%02x\n", ret);
    return ret;
}

static UCHAR nbCall(NetBIOSAdapter *adapter, PNCB ncb)
{
    UCHAR ret, i;

    TRACE(": adapter %p, NCB %p\n", adapter, ncb);

    if (!adapter) return NRC_BRIDGE;
    if (adapter->sessionsLen == 0) return NRC_ENVNOTDEF;
    if (!adapter->transport->call) return NRC_ILLCMD;
    if (!ncb) return NRC_INVADDRESS;

    EnterCriticalSection(&adapter->cs);
    for (i = 0; i < adapter->sessionsLen && adapter->sessions[i].inUse; i++)
        ;
    if (i < adapter->sessionsLen)
    {
        adapter->sessions[i].inUse = TRUE;
        adapter->sessions[i].state = CALL_PENDING;
        memcpy(adapter->sessions[i].local_name, ncb->ncb_name, NCBNAMSZ);
        memcpy(adapter->sessions[i].remote_name, ncb->ncb_callname, NCBNAMSZ);
        ret = NRC_GOODRET;
    }
    else
        ret = NRC_LOCTFUL;
    LeaveCriticalSection(&adapter->cs);

    if (ret == NRC_GOODRET)
    {
        ret = adapter->transport->call(adapter->impl.data, ncb,
         &adapter->sessions[i].data);
        if (ret == NRC_GOODRET)
        {
            ncb->ncb_lsn = i;
            adapter->sessions[i].state = SESSION_ESTABLISHED;
        }
        else
        {
            adapter->sessions[i].inUse = FALSE;
            adapter->sessions[i].state = 0;
        }
    }
    TRACE("returning 0x%02x\n", ret);
    return ret;
}

static UCHAR nbSend(NetBIOSAdapter *adapter, PNCB ncb)
{
    UCHAR ret;
    NetBIOSSession *session;

    if (!adapter) return NRC_BRIDGE;
    if (!adapter->transport->send) return NRC_ILLCMD;
    if (!ncb) return NRC_INVADDRESS;
    if (ncb->ncb_lsn >= adapter->sessionsLen) return NRC_SNUMOUT;
    if (!adapter->sessions[ncb->ncb_lsn].inUse) return NRC_SNUMOUT;
    if (!ncb->ncb_buffer) return NRC_BADDR;

    session = &adapter->sessions[ncb->ncb_lsn];
    if (session->state != SESSION_ESTABLISHED)
        ret = NRC_SNUMOUT;
    else
        ret = adapter->transport->send(adapter->impl.data, session->data, ncb);
    return ret;
}

static UCHAR nbRecv(NetBIOSAdapter *adapter, PNCB ncb)
{
    UCHAR ret;
    NetBIOSSession *session;

    if (!adapter) return NRC_BRIDGE;
    if (!adapter->transport->recv) return NRC_ILLCMD;
    if (!ncb) return NRC_INVADDRESS;
    if (ncb->ncb_lsn >= adapter->sessionsLen) return NRC_SNUMOUT;
    if (!adapter->sessions[ncb->ncb_lsn].inUse) return NRC_SNUMOUT;
    if (!ncb->ncb_buffer) return NRC_BADDR;

    session = &adapter->sessions[ncb->ncb_lsn];
    if (session->state != SESSION_ESTABLISHED)
        ret = NRC_SNUMOUT;
    else
        ret = adapter->transport->recv(adapter->impl.data, session->data, ncb);
    return ret;
}

static UCHAR nbInternalHangup(NetBIOSAdapter *adapter, NetBIOSSession *session)
{
    UCHAR ret;

    if (!adapter) return NRC_BRIDGE;
    if (!session) return NRC_SNUMOUT;

    if (adapter->transport->hangup)
        ret = adapter->transport->hangup(adapter->impl.data, session->data);
    else
        ret = NRC_ILLCMD;
    EnterCriticalSection(&adapter->cs);
    memset(session, 0, sizeof(NetBIOSSession));
    LeaveCriticalSection(&adapter->cs);
    return ret;
}

static UCHAR nbHangup(NetBIOSAdapter *adapter, const NCB *ncb)
{
    UCHAR ret;
    NetBIOSSession *session;

    if (!adapter) return NRC_BRIDGE;
    if (!ncb) return NRC_INVADDRESS;
    if (ncb->ncb_lsn >= adapter->sessionsLen) return NRC_SNUMOUT;
    if (!adapter->sessions[ncb->ncb_lsn].inUse) return NRC_SNUMOUT;

    session = &adapter->sessions[ncb->ncb_lsn];
    if (session->state != SESSION_ESTABLISHED)
        ret = NRC_SNUMOUT;
    else
    {
        session->state = HANGUP_PENDING;
        ret = nbInternalHangup(adapter, session);
    }
    return ret;
}

void NetBIOSHangupSession(const NCB *ncb)
{
    NetBIOSAdapter *adapter;

    if (!ncb) return;

    adapter = nbGetAdapter(ncb->ncb_lana_num);
    if (adapter)
    {
        if (ncb->ncb_lsn < adapter->sessionsLen &&
         adapter->sessions[ncb->ncb_lsn].inUse)
            nbHangup(adapter, ncb);
    }
}

static UCHAR nbAStat(NetBIOSAdapter *adapter, PNCB ncb)
{
    UCHAR ret;

    if (!adapter) return NRC_BRIDGE;
    if (!adapter->transport->astat) return NRC_ILLCMD;
    if (!ncb) return NRC_INVADDRESS;
    if (!ncb->ncb_buffer) return NRC_BADDR;
    if (ncb->ncb_length < sizeof(ADAPTER_STATUS)) return NRC_BUFLEN;

    ret = adapter->transport->astat(adapter->impl.data, ncb);
    if (ncb->ncb_callname[0] == '*')
    {
        PADAPTER_STATUS astat = (PADAPTER_STATUS)ncb->ncb_buffer;

        astat->max_sess = astat->max_cfg_sess = adapter->sessionsLen;
    }
    return ret;
}

static UCHAR nbDispatch(NetBIOSAdapter *adapter, PNCB ncb)
{
    UCHAR ret, cmd;

    TRACE(": adapter %p, ncb %p\n", adapter, ncb);

    if (!adapter) return NRC_BRIDGE;
    if (!ncb) return NRC_INVADDRESS;

    cmd = ncb->ncb_command & 0x7f;
    if (cmd == NCBRESET)
        ret = nbReset(adapter, ncb);
    else
    {
        ret = NBCmdQueueAdd(adapter->cmdQueue, ncb);
        if (ret == NRC_GOODRET)
        {
            switch (cmd)
            {
                case NCBCALL:
                    ret = nbCall(adapter, ncb);
                    break;

                /* WinNT doesn't chain sends, it always sends immediately.
                 * Doubt there's any real significance to the NA variants.
                 */
                case NCBSEND:
                case NCBSENDNA:
                case NCBCHAINSEND:
                case NCBCHAINSENDNA:
                    ret = nbSend(adapter, ncb);
                    break;

                case NCBRECV:
                    ret = nbRecv(adapter, ncb);
                    break;

                case NCBHANGUP:
                    ret = nbHangup(adapter, ncb);
                    break;

                case NCBASTAT:
                    ret = nbAStat(adapter, ncb);
                    break;

                case NCBFINDNAME:
                    if (adapter->transport->findName)
                        ret = adapter->transport->findName(adapter->impl.data,
                         ncb);
                    else
                        ret = NRC_ILLCMD;
                    break;

                default:
                    FIXME("(%p): command code 0x%02x\n", ncb, ncb->ncb_command);
                    ret = NRC_ILLCMD;
            }
            NBCmdQueueComplete(adapter->cmdQueue, ncb, ret);
        }
    }
    TRACE("returning 0x%02x\n", ret);
    return ret;
}

static DWORD WINAPI nbCmdThread(LPVOID lpVoid)
{
    PNCB ncb = lpVoid;

    if (ncb)
    {
        UCHAR ret;
        NetBIOSAdapter *adapter = nbGetAdapter(ncb->ncb_lana_num);

        if (adapter)
            ret = nbDispatch(adapter, ncb);
        else
            ret = NRC_BRIDGE;
        ncb->ncb_retcode = ncb->ncb_cmd_cplt = ret;
        if (ncb->ncb_post)
            ncb->ncb_post(ncb);
        else if (ncb->ncb_event)
            SetEvent(ncb->ncb_event);
    }
    return 0;
}

UCHAR WINAPI Netbios(PNCB ncb)
{
    UCHAR ret, cmd;

    TRACE("ncb = %p\n", ncb);

    if (!ncb) return NRC_INVADDRESS;

    TRACE("ncb_command 0x%02x, ncb_lana_num %d, ncb_buffer %p, ncb_length %d\n",
     ncb->ncb_command, ncb->ncb_lana_num, ncb->ncb_buffer, ncb->ncb_length);
    cmd = ncb->ncb_command & 0x7f;

    if (cmd == NCBENUM)
        ncb->ncb_retcode = ncb->ncb_cmd_cplt = ret = nbEnum(ncb);
    else if (cmd == NCBADDNAME)
    {
        FIXME("NCBADDNAME: stub, returning success\n");
        ncb->ncb_retcode = ncb->ncb_cmd_cplt = ret = NRC_GOODRET;
    }
    else
    {
        NetBIOSAdapter *adapter;

        /* Apps not specifically written for WinNT won't do an NCBENUM first,
         * so make sure the table has been enumerated at least once
         */
        if (!gNBTable.enumerated)
            nbInternalEnum();
        adapter = nbGetAdapter(ncb->ncb_lana_num);
        if (!adapter)
            ret = NRC_BRIDGE;
        else
        {
            if (adapter->shuttingDown)
                ret = NRC_IFBUSY;
            else if (adapter->resetting)
                ret = NRC_TOOMANY;
            else
            {
                /* non-asynch commands first */
                if (cmd == NCBCANCEL)
                    ncb->ncb_retcode = ncb->ncb_cmd_cplt = ret =
                     nbCancel(adapter, ncb);
                else if (cmd == NCBSSTAT)
                    ncb->ncb_retcode = ncb->ncb_cmd_cplt = ret =
                     nbSStat(adapter, ncb);
                else
                {
                    if (ncb->ncb_command & ASYNCH)
                    {
                        HANDLE thread = CreateThread(NULL, 0, nbCmdThread, ncb,
                         CREATE_SUSPENDED, NULL);

                        if (thread != NULL)
                        {
                            ncb->ncb_retcode = ncb->ncb_cmd_cplt = NRC_PENDING;
                            if (ncb->ncb_event)
                                ResetEvent(ncb->ncb_event);
                            ResumeThread(thread);
                            CloseHandle(thread);
                            ret = NRC_GOODRET;
                        }
                        else
                        ncb->ncb_retcode = ncb->ncb_cmd_cplt = ret =
                         NRC_OSRESNOTAV;
                    }
                    else
                        ncb->ncb_retcode = ncb->ncb_cmd_cplt = ret =
                         nbDispatch(adapter, ncb);
                }
            }
        }
    }
    TRACE("returning 0x%02x\n", ret);
    return ret;
}