/*++

Copyright (c) 2016 Minoca Corp. All Rights Reserved

Module Name:

    netlink.c

Abstract:

    This module implements the generic netlink 802.11 family message handling.

Author:

    Chris Stevens 29-Mar-2016

Environment:

    Kernel

--*/

//
// ------------------------------------------------------------------- Includes
//

#include "net80211.h"
#include <minoca/net/netlink.h>

//
// ---------------------------------------------------------------- Definitions
//

//
// Define the multicast group indices. These must match the order of the
// multicast group array below.
//

#define NETLINK_GENERIC_80211_MULTICAST_SCAN 0

//
// ------------------------------------------------------ Data Type Definitions
//

//
// ----------------------------------------------- Internal Function Prototypes
//

KSTATUS
Net80211pNetlinkJoin (
    PNET_SOCKET Socket,
    PNET_PACKET_BUFFER Packet,
    PNETLINK_GENERIC_COMMAND_INFORMATION Command
    );

KSTATUS
Net80211pNetlinkLeave (
    PNET_SOCKET Socket,
    PNET_PACKET_BUFFER Packet,
    PNETLINK_GENERIC_COMMAND_INFORMATION Command
    );

KSTATUS
Net80211pNetlinkScanStart (
    PNET_SOCKET Socket,
    PNET_PACKET_BUFFER Packet,
    PNETLINK_GENERIC_COMMAND_INFORMATION Command
    );

VOID
Net80211pNetlinkScanCompletionRoutine (
    PNET80211_LINK Link,
    KSTATUS ScanStatus
    );

KSTATUS
Net80211pNetlinkScanGetResults (
    PNET_SOCKET Socket,
    PNET_PACKET_BUFFER Packet,
    PNETLINK_GENERIC_COMMAND_INFORMATION Command
    );

KSTATUS
Net80211pNetlinkGetLink (
    PNET_PACKET_BUFFER Packet,
    PNET80211_LINK *Link
    );

VOID
Net80211pNetlinkSendScanNotification (
    PNET80211_LINK Link,
    UCHAR Command
    );

//
// -------------------------------------------------------------------- Globals
//

NETLINK_GENERIC_COMMAND Net80211NetlinkCommands[] = {
    {
        NETLINK_80211_COMMAND_JOIN,
        0,
        Net80211pNetlinkJoin
    },

    {
        NETLINK_80211_COMMAND_LEAVE,
        0,
        Net80211pNetlinkLeave
    },

    {
        NETLINK_80211_COMMAND_SCAN_START,
        0,
        Net80211pNetlinkScanStart
    },

    {
        NETLINK_80211_COMMAND_SCAN_GET_RESULTS,
        NETLINK_HEADER_FLAG_DUMP,
        Net80211pNetlinkScanGetResults
    },
};

NETLINK_GENERIC_MULTICAST_GROUP Net80211NetlinkMulticastGroups[] = {
    {
        NETLINK_GENERIC_80211_MULTICAST_SCAN,
        sizeof(NETLINK_80211_MULTICAST_SCAN_NAME),
        NETLINK_80211_MULTICAST_SCAN_NAME
    },
};

NETLINK_GENERIC_FAMILY_PROPERTIES Net80211NetlinkFamilyProperties = {
    NETLINK_GENERIC_FAMILY_PROPERTIES_VERSION,
    0,
    sizeof(NETLINK_GENERIC_80211_NAME),
    NETLINK_GENERIC_80211_NAME,
    Net80211NetlinkCommands,
    sizeof(Net80211NetlinkCommands) / sizeof(Net80211NetlinkCommands[0]),
    Net80211NetlinkMulticastGroups,
    sizeof(Net80211NetlinkMulticastGroups) /
        sizeof(Net80211NetlinkMulticastGroups[0]),
};

PNETLINK_GENERIC_FAMILY Net80211NetlinkFamily = NULL;

//
// ------------------------------------------------------------------ Functions
//

KSTATUS
Net80211pNetlinkInitialize (
    VOID
    )

/*++

Routine Description:

    This routine initializes the generic netlink 802.11 family.

Arguments:

    None.

Return Value:

    Status code.

--*/

{

    KSTATUS Status;

    Status = NetlinkGenericRegisterFamily(&Net80211NetlinkFamilyProperties,
                                          &Net80211NetlinkFamily);

    return Status;
}

VOID
Net80211pNetlinkDestroy (
    VOID
    )

/*++

Routine Description:

    This routine tears down support for the generic netlink 802.11 family.

Arguments:

    None.

Return Value:

    None.

--*/

{

    if (Net80211NetlinkFamily != NULL) {
        NetlinkGenericUnregisterFamily(Net80211NetlinkFamily);
        Net80211NetlinkFamily = NULL;
    }

    return;
}

//
// --------------------------------------------------------- Internal Functions
//

KSTATUS
Net80211pNetlinkJoin (
    PNET_SOCKET Socket,
    PNET_PACKET_BUFFER Packet,
    PNETLINK_GENERIC_COMMAND_INFORMATION Command
    )

/*++

Routine Description:

    This routine is called to process an 802.11 netlink network join request.
    It attempts to join a device to a network as specified by the netlink
    message.

Arguments:

    Socket - Supplies a pointer to the socket that received the packet.

    Packet - Supplies a pointer to a structure describing the incoming packet.
        This structure may be used as a scratch space while this routine
        executes and the packet travels up the stack, but will not be accessed
        after this routine returns.

    Command - Supplies a pointer to the command information.

Return Value:

    Status code.

--*/

{

    PVOID Attributes;
    ULONG AttributesLength;
    PVOID Bssid;
    USHORT BssidLength;
    PNET80211_LINK Link;
    PUCHAR Passphrase;
    USHORT PassphraseLength;
    NET80211_SCAN_STATE ScanParameters;
    PUCHAR Ssid;
    USHORT SsidLength;
    KSTATUS Status;

    //
    // Parse the packet to find the 802.11 link that is to join a network.
    //

    Status = Net80211pNetlinkGetLink(Packet, &Link);
    if (!KSUCCESS(Status)) {
        goto NetlinkJoinEnd;
    }

    //
    // An SSID is necessary even if a BSSID is supplied. The user shouldn't
    // join an BSSID that switched SSID's on it.
    //

    Attributes = Packet->Buffer + Packet->DataOffset;
    AttributesLength = Packet->FooterOffset - Packet->DataOffset;
    Status = NetlinkGetAttribute(Attributes,
                                 AttributesLength,
                                 NETLINK_80211_ATTRIBUTE_SSID,
                                 (PVOID *)&Ssid,
                                 &SsidLength);

    if (!KSUCCESS(Status)) {
        goto NetlinkJoinEnd;
    }

    //
    // The attribute should store a null-terminated SSID string. Fail if it is
    // not present and strip it if it is present. The scan parameters do not
    // take a null-terminator SSID.
    //

    if (Ssid[SsidLength - 1] != STRING_TERMINATOR) {
        Status = STATUS_INVALID_PARAMETER;
        goto NetlinkJoinEnd;
    }

    SsidLength -= 1;
    if (SsidLength > NET80211_MAX_SSID_LENGTH) {
        Status = STATUS_NAME_TOO_LONG;
        goto NetlinkJoinEnd;
    }

    //
    // The passphrase is optional as some network do not require them. Make
    // sure it is null-terminated and strip the null character if it is.
    //

    Status = NetlinkGetAttribute(Attributes,
                                 AttributesLength,
                                 NETLINK_80211_ATTRIBUTE_PASSPHRASE,
                                 (PVOID *)&Passphrase,
                                 &PassphraseLength);

    if (KSUCCESS(Status)) {
        if (Passphrase[PassphraseLength - 1] != STRING_TERMINATOR) {
            Status = STATUS_INVALID_PARAMETER;
            goto NetlinkJoinEnd;
        }

        PassphraseLength -= 1;
        if (PassphraseLength > NET80211_MAX_PASSPHRASE_LENGTH) {
            Status = STATUS_NAME_TOO_LONG;
            goto NetlinkJoinEnd;
        }
    }

    //
    // The BSSID is optional as the MAC address of the access point is not
    // always known.
    //

    Status = NetlinkGetAttribute(Attributes,
                                 AttributesLength,
                                 NETLINK_80211_ATTRIBUTE_BSSID,
                                 &Bssid,
                                 &BssidLength);

    if (KSUCCESS(Status)) {
        if (BssidLength != NET80211_ADDRESS_SIZE) {
            Status = STATUS_INVALID_PARAMETER;
            goto NetlinkJoinEnd;
        }
    }

    RtlZeroMemory(&ScanParameters, sizeof(NET80211_SCAN_STATE));
    ScanParameters.Link = Link;
    ScanParameters.Flags = NET80211_SCAN_FLAG_JOIN;
    if (BssidLength == 0) {
        ScanParameters.Flags |= NET80211_SCAN_FLAG_BROADCAST;

    } else {
        RtlCopyMemory(ScanParameters.Bssid, Bssid, BssidLength);
    }

    if (PassphraseLength != 0) {
        ScanParameters.PassphraseLength = PassphraseLength;
        RtlCopyMemory(ScanParameters.Passphrase, Passphrase, PassphraseLength);
    }

    RtlCopyMemory(ScanParameters.Ssid, Ssid, SsidLength);
    ScanParameters.SsidLength = SsidLength;
    Status = Net80211pStartScan(Link, &ScanParameters);
    if (!KSUCCESS(Status)) {
        goto NetlinkJoinEnd;
    }

NetlinkJoinEnd:
    if (Link != NULL) {
        Net80211LinkReleaseReference(Link);
    }

    return Status;
}

KSTATUS
Net80211pNetlinkLeave (
    PNET_SOCKET Socket,
    PNET_PACKET_BUFFER Packet,
    PNETLINK_GENERIC_COMMAND_INFORMATION Command
    )

/*++

Routine Description:

    This routine is called to process an 802.11 netlink request for a device to
    leave its current network.

Arguments:

    Socket - Supplies a pointer to the socket that received the packet.

    Packet - Supplies a pointer to a structure describing the incoming packet.
        This structure may be used as a scratch space while this routine
        executes and the packet travels up the stack, but will not be accessed
        after this routine returns.

    Command - Supplies a pointer to the command information.

Return Value:

    Status code.

--*/

{

    PNET80211_LINK Link;
    KSTATUS Status;

    //
    // Parse the packet to find the 802.11 link that is to leave its network.
    //

    Status = Net80211pNetlinkGetLink(Packet, &Link);
    if (!KSUCCESS(Status)) {
        goto NetlinkLeaveEnd;
    }

    //
    // Setting the link state to initialized will deactivate the current
    // connection and send the appropriate deactivation messages to the access
    // point.
    //

    Net80211pSetState(Link, Net80211StateInitialized);

NetlinkLeaveEnd:
    if (Link != NULL) {
        Net80211LinkReleaseReference(Link);
    }

    return Status;
}

KSTATUS
Net80211pNetlinkScanStart (
    PNET_SOCKET Socket,
    PNET_PACKET_BUFFER Packet,
    PNETLINK_GENERIC_COMMAND_INFORMATION Command
    )

/*++

Routine Description:

    This routine is called to process an 802.11 netlink request to start
    scanning for available wireless networks.

Arguments:

    Socket - Supplies a pointer to the socket that received the packet.

    Packet - Supplies a pointer to a structure describing the incoming packet.
        This structure may be used as a scratch space while this routine
        executes and the packet travels up the stack, but will not be accessed
        after this routine returns.

    Command - Supplies a pointer to the command information.

Return Value:

    Status code.

--*/

{

    PNET80211_LINK Link;
    NET80211_SCAN_STATE ScanParameters;
    KSTATUS Status;

    //
    // Parse the packet to find the 802.11 link that is to scan for networks.
    //

    Status = Net80211pNetlinkGetLink(Packet, &Link);
    if (!KSUCCESS(Status)) {
        goto NetlinkScanStartEnd;
    }

    //
    // Kick off a background scan to update the BSS cache for this link.
    //

    RtlZeroMemory(&ScanParameters, sizeof(NET80211_SCAN_STATE));
    ScanParameters.Link = Link;
    ScanParameters.Flags = NET80211_SCAN_FLAG_BROADCAST;
    ScanParameters.CompletionRoutine = Net80211pNetlinkScanCompletionRoutine;
    Status = Net80211pStartScan(Link, &ScanParameters);
    if (!KSUCCESS(Status)) {
        goto NetlinkScanStartEnd;
    }

    //
    // Notify the scan multicast group that this scan is starting.
    //

    Net80211pNetlinkSendScanNotification(Link,
                                         NETLINK_80211_COMMAND_SCAN_START);

NetlinkScanStartEnd:
    if (Link != NULL) {
        Net80211LinkReleaseReference(Link);
    }

    return Status;
}

VOID
Net80211pNetlinkScanCompletionRoutine (
    PNET80211_LINK Link,
    KSTATUS ScanStatus
    )

/*++

Routine Description:

    This routine is called when a scan for nearby BSS access points has
    completed.

Arguments:

    Link - Supplies a pointer to the 802.11 link that performed the scan.

    ScanStatus - Supplies the status result of the scan.

Return Value:

    None.

--*/

{

    UCHAR Command;

    //
    // Report success or failure without any further details on an error.
    //

    Command = NETLINK_80211_COMMAND_SCAN_RESULT;
    if (!KSUCCESS(ScanStatus)) {
        Command = NETLINK_80211_COMMAND_SCAN_ABORTED;
    }

    Net80211pNetlinkSendScanNotification(Link, Command);
    return;
}

KSTATUS
Net80211pNetlinkScanGetResults (
    PNET_SOCKET Socket,
    PNET_PACKET_BUFFER Packet,
    PNETLINK_GENERIC_COMMAND_INFORMATION Command
    )

/*++

Routine Description:

    This routine gets the results from the latest scan, packages them up as
    a netlink message and sends them back to the caller.

Arguments:

    Socket - Supplies a pointer to the socket that received the packet.

    Packet - Supplies a pointer to a structure describing the incoming packet.
        This structure may be used as a scratch space while this routine
        executes and the packet travels up the stack, but will not be accessed
        after this routine returns.

    Command - Supplies a pointer to the command information.

Return Value:

    Status code.

--*/

{

    PNET80211_BSS_ENTRY Bss;
    ULONG BssCount;
    ULONG BssLength;
    ULONG BssStatus;
    PLIST_ENTRY CurrentEntry;
    DEVICE_ID DeviceId;
    PNET80211_LINK Link;
    ULONG ResultLength;
    PNET_PACKET_BUFFER Results;
    ULONG ResultsLength;
    LONG SignalMbm;
    KSTATUS Status;

    //
    // Parse the packet to find the 802.11 link whose scan results are to be
    // queried.
    //

    Status = Net80211pNetlinkGetLink(Packet, &Link);
    if (!KSUCCESS(Status)) {
        goto NetlinkScanGetResultsEnd;
    }

    //
    // Determine the required size of the BSS replies.
    //

    BssCount = 0;
    ResultsLength = 0;
    KeAcquireQueuedLock(Link->Lock);
    CurrentEntry = Link->BssList.Next;
    while (CurrentEntry != &(Link->BssList)) {
        Bss = LIST_VALUE(CurrentEntry, NET80211_BSS_ENTRY, ListEntry);
        CurrentEntry = CurrentEntry->Next;
        ResultsLength += NETLINK_HEADER_LENGTH + NETLINK_GENERIC_HEADER_LENGTH;
        ResultsLength += NETLINK_ATTRIBUTE_SIZE(sizeof(DEVICE_ID));
        BssLength = NETLINK_ATTRIBUTE_SIZE(NET80211_ADDRESS_SIZE) +
                    NETLINK_ATTRIBUTE_SIZE(sizeof(Bss->State.Capabilities)) +
                    NETLINK_ATTRIBUTE_SIZE(sizeof(Bss->State.BeaconInterval)) +
                    NETLINK_ATTRIBUTE_SIZE(sizeof(BssStatus)) +
                    NETLINK_ATTRIBUTE_SIZE(sizeof(SignalMbm)) +
                    NETLINK_ATTRIBUTE_SIZE(Bss->ElementsSize);

        ResultsLength += NETLINK_ATTRIBUTE_SIZE(BssLength);
        BssCount += 1;
    }

    KeReleaseQueuedLock(Link->Lock);

    //
    // At least always send a done message.
    //

    ResultsLength += NETLINK_HEADER_LENGTH;

    //
    // Allocate the network packet buffer to hold all of the results.
    //

    Status = NetAllocateBuffer(0, ResultsLength, 0, NULL, 0, &Results);
    if (!KSUCCESS(Status)) {
        goto NetlinkScanGetResultsEnd;
    }

    //
    // Package up the BSS entry list into multiple netlink scan result messages.
    // If new entries arrived since the lock was held before, they came in due
    // to an additional scan. A future scan results request will package them
    // up.
    //

    DeviceId = IoGetDeviceNumericId(Link->Properties.Device);
    KeAcquireQueuedLock(Link->Lock);
    CurrentEntry = Link->BssList.Next;
    while ((CurrentEntry != &(Link->BssList)) && (BssCount != 0)) {
        Bss = LIST_VALUE(CurrentEntry, NET80211_BSS_ENTRY, ListEntry);
        CurrentEntry = CurrentEntry->Next;

        //
        // Determine the length of the entire message.
        //

        ResultLength = NETLINK_ATTRIBUTE_SIZE(sizeof(DEVICE_ID));
        BssLength = NETLINK_ATTRIBUTE_SIZE(NET80211_ADDRESS_SIZE) +
                    NETLINK_ATTRIBUTE_SIZE(sizeof(Bss->State.Capabilities)) +
                    NETLINK_ATTRIBUTE_SIZE(sizeof(Bss->State.BeaconInterval)) +
                    NETLINK_ATTRIBUTE_SIZE(sizeof(BssStatus)) +
                    NETLINK_ATTRIBUTE_SIZE(sizeof(SignalMbm)) +
                    NETLINK_ATTRIBUTE_SIZE(Bss->ElementsSize);

        ResultLength += NETLINK_ATTRIBUTE_SIZE(BssLength);

        //
        // Add a generic and base header for this entry.
        //

        Status = NetlinkGenericAppendHeaders(Net80211NetlinkFamily,
                                             Results,
                                             ResultLength,
                                             Command->Message.SequenceNumber,
                                             NETLINK_HEADER_FLAG_MULTIPART,
                                             NETLINK_80211_COMMAND_SCAN_RESULT,
                                             0);

        if (!KSUCCESS(Status)) {
            goto NetlinkScanGetResultsEnd;
        }

        //
        // Add the attributes.
        //

        Status = NetlinkAppendAttribute(Results,
                                        NETLINK_80211_ATTRIBUTE_DEVICE_ID,
                                        &DeviceId,
                                        sizeof(DEVICE_ID));

        if (!KSUCCESS(Status)) {
            goto NetlinkScanGetResultsEnd;
        }

        Status = NetlinkAppendAttribute(Results,
                                        NETLINK_80211_ATTRIBUTE_BSS,
                                        NULL,
                                        BssLength);

        if (!KSUCCESS(Status)) {
            goto NetlinkScanGetResultsEnd;
        }

        Status = NetlinkAppendAttribute(Results,
                                        NETLINK_80211_BSS_ATTRIBUTE_BSSID,
                                        Bss->State.Bssid,
                                        NET80211_ADDRESS_SIZE);

        if (!KSUCCESS(Status)) {
            goto NetlinkScanGetResultsEnd;
        }

        Status = NetlinkAppendAttribute(Results,
                                        NETLINK_80211_BSS_ATTRIBUTE_CAPABILITY,
                                        &(Bss->State.Capabilities),
                                        sizeof(Bss->State.Capabilities));

        if (!KSUCCESS(Status)) {
            goto NetlinkScanGetResultsEnd;
        }

        Status = NetlinkAppendAttribute(
                                   Results,
                                   NETLINK_80211_BSS_ATTRIBUTE_BEACON_INTERVAL,
                                   &(Bss->State.BeaconInterval),
                                   sizeof(Bss->State.BeaconInterval));

        if (!KSUCCESS(Status)) {
            goto NetlinkScanGetResultsEnd;
        }

        SignalMbm = Bss->State.Rssi * 100;
        Status = NetlinkAppendAttribute(Results,
                                        NETLINK_80211_BSS_ATTRIBUTE_SIGNAL_MBM,
                                        &SignalMbm,
                                        sizeof(SignalMbm));

        if (!KSUCCESS(Status)) {
            goto NetlinkScanGetResultsEnd;
        }

        BssStatus = NETLINK_80211_BSS_STATUS_NOT_CONNECTED;
        if (Bss == Link->ActiveBss) {
            switch (Link->State) {
            case Net80211StateAssociated:
            case Net80211StateEncrypted:
                Status = NETLINK_80211_BSS_STATUS_ASSOCIATED;
                break;

            case Net80211StateAssociating:
                Status = NETLINK_80211_BSS_STATUS_AUTHENTICATED;
                break;

            default:
                break;
            }
        }

        Status = NetlinkAppendAttribute(Results,
                                        NETLINK_80211_BSS_ATTRIBUTE_STATUS,
                                        &BssStatus,
                                        sizeof(BssStatus));

        if (!KSUCCESS(Status)) {
            goto NetlinkScanGetResultsEnd;
        }

        Status = NetlinkAppendAttribute(
                              Results,
                              NETLINK_80211_BSS_ATTRIBUTE_INFORMATION_ELEMENTS,
                              Bss->Elements,
                              Bss->ElementsSize);

        if (!KSUCCESS(Status)) {
            goto NetlinkScanGetResultsEnd;
        }

        BssCount -= 1;
    }

    KeReleaseQueuedLock(Link->Lock);

    //
    // Send this multipart message back to the source of the request. This
    // routine will add the terminating DONE message and then send the entire
    // set of messages in the results packet.
    //

    Status = NetlinkSendMultipartMessage(Socket,
                                         Results,
                                         Command->Message.SourceAddress,
                                         Command->Message.SequenceNumber);

    if (!KSUCCESS(Status)) {
        goto NetlinkScanGetResultsEnd;
    }

NetlinkScanGetResultsEnd:
    if (Link != NULL) {
        Net80211LinkReleaseReference(Link);
    }

    return Status;
}

KSTATUS
Net80211pNetlinkGetLink (
    PNET_PACKET_BUFFER Packet,
    PNET80211_LINK *Link
    )

/*++

Routine Description:

    This routine parses the given 802.11 netlink message packet for the device
    ID attribute and then looks for the corresponding 802.11 link. If found, a
    reference is taken on the the 802.11 link and it is the caller's
    responsibility to release the reference.

Arguments:

    Packet - Supplies a pointer to the netlink message to parse for the device
        ID or order to determine the target 802.11 link.

    Link - Supplies a pointer that receives a pointer an 802.11 network link.

Return Value:

    Status code.

--*/

{

    PVOID Attributes;
    ULONG AttributesLength;
    PDEVICE Device;
    PVOID DeviceId;
    USHORT DeviceIdLength;
    PNET80211_LINK Net80211Link;
    PNET_LINK NetLink;
    KSTATUS Status;

    Device = NULL;
    NetLink = NULL;
    Net80211Link = NULL;

    //
    // Get the device ID. It is necessary to find the appropriate link.
    //

    Attributes = Packet->Buffer + Packet->DataOffset;
    AttributesLength = Packet->FooterOffset - Packet->DataOffset;
    Status = NetlinkGetAttribute(Attributes,
                                 AttributesLength,
                                 NETLINK_80211_ATTRIBUTE_DEVICE_ID,
                                 &DeviceId,
                                 &DeviceIdLength);

    if (!KSUCCESS(Status)) {
        goto NetlinkGetLinkEnd;
    }

    if (DeviceIdLength != sizeof(DEVICE_ID)) {
        Status = STATUS_DATA_LENGTH_MISMATCH;
        goto NetlinkGetLinkEnd;
    }

    Device = IoGetDeviceByNumericId(*((PDEVICE_ID)DeviceId));
    if (Device == NULL) {
        Status = STATUS_NO_SUCH_DEVICE;
        goto NetlinkGetLinkEnd;
    }

    Status = NetLookupLinkByDevice(Device, &NetLink);
    if (!KSUCCESS(Status)) {
        goto NetlinkGetLinkEnd;
    }

    //
    // If the link is not an 802.11 type then nothing can be done.
    //

    if (NetLink->Properties.DataLinkType != NetDomain80211) {
        Status = STATUS_NOT_SUPPORTED;
        goto NetlinkGetLinkEnd;
    }

    //
    // Setting the link state to initialized will deactivate the current
    // connection and send the appropriate deactivation messages to the access
    // point.
    //

    Net80211Link = NetLink->DataLinkContext;
    Net80211LinkAddReference(Net80211Link);

NetlinkGetLinkEnd:
    if (Device != NULL) {
        IoDeviceReleaseReference(Device);
    }

    if (NetLink != NULL) {
        NetLinkReleaseReference(NetLink);
    }

    *Link = Net80211Link;
    return Status;
}

VOID
Net80211pNetlinkSendScanNotification (
    PNET80211_LINK Link,
    UCHAR Command
    )

/*++

Routine Description:

    This routine notifies the scan multicast group about a scan's progress.

Arguments:

    Link - Supplies a pointer to the 802.11 link that performed the scan.

    Command - Supplies the 802.11 netlink command to send to the scan multicast
        group.

Return Value:

    None.

--*/

{

    DEVICE_ID DeviceId;
    ULONG HeaderSize;
    PNET_PACKET_BUFFER Packet;
    ULONG PayloadSize;
    ULONG Size;
    KSTATUS Status;

    //
    // Allocate and build a network buffer to hold the scan properties.
    //

    Packet = NULL;
    HeaderSize = NETLINK_HEADER_LENGTH + NETLINK_GENERIC_HEADER_LENGTH;
    PayloadSize = NETLINK_ATTRIBUTE_SIZE(sizeof(DEVICE_ID));
    Size = HeaderSize + PayloadSize;
    Status = NetAllocateBuffer(0,
                               Size,
                               0,
                               NULL,
                               0,
                               &Packet);

    if (!KSUCCESS(Status)) {
        goto NetlinkSendScanNotification;
    }

    Status = NetlinkGenericAppendHeaders(Net80211NetlinkFamily,
                                         Packet,
                                         PayloadSize,
                                         0,
                                         0,
                                         Command,
                                         0);

    if (!KSUCCESS(Status)) {
        goto NetlinkSendScanNotification;
    }

    DeviceId = IoGetDeviceNumericId(Link->Properties.Device);
    Status = NetlinkAppendAttribute(Packet,
                                    NETLINK_80211_ATTRIBUTE_DEVICE_ID,
                                    &DeviceId,
                                    sizeof(DEVICE_ID));

    if (!KSUCCESS(Status)) {
        goto NetlinkSendScanNotification;
    }

    //
    // Send the packet out to the 802.11 scan multicast group.
    //

    Status = NetlinkGenericSendMulticastCommand(
                                         Net80211NetlinkFamily,
                                         Packet,
                                         NETLINK_GENERIC_80211_MULTICAST_SCAN);

    if (!KSUCCESS(Status)) {
        goto NetlinkSendScanNotification;
    }

NetlinkSendScanNotification:
    if (Packet != NULL) {
        NetFreeBuffer(Packet);
    }

    return;
}

