// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil -*- (for GNU Emacs)
//
// Copyright (c) 1985-2000 Microsoft Corporation
//
// This file is part of the Microsoft Research IPv6 Network Protocol Stack.
// You should have received a copy of the Microsoft End-User License Agreement
// for this software along with this release; see the file "license.txt".
// If not, please see http://www.research.microsoft.com/msripv6/license.htm,
// or write to Microsoft Research, One Microsoft Way, Redmond, WA 98052-6399.
//
// Abstract:
//
// Various subroutines for Internet Protocol Version 6 
//


#include "oscfg.h"
#include "ndis.h"
#include "tdi.h"
#include "tdistat.h"
#include "tdikrnl.h"
#include "ip6imp.h"
#include "ip6def.h"
#include "fragment.h"
#include "icmp.h"
#include "neighbor.h"
#include "route.h"
#include "mld.h"
#include "md5.h"

uint IPv6TickCount = 0;

uint RandomValue = 0;


//* SeedRandom - Provide a seed value.
//
//  Called to provide a seed value for the random number generator.
//
void
SeedRandom(const uchar *Seed, uint Length)
{
    uint OldValue;
    MD5_CTX Context;
    union {
        uint NewValue;
        uchar Buffer[16];
    } Hash;

    do {
        OldValue = RandomValue;
        MD5Init(&Context);
        MD5Update(&Context, (uchar *)Seed, Length);
        MD5Update(&Context, (uchar *)&OldValue, sizeof OldValue);
        MD5Final(&Context);
        memcpy(Hash.Buffer, Context.digest, MD5DIGESTLEN);
    } while (InterlockedCompareExchange((PLONG)&RandomValue,
                                        (LONG)Hash.NewValue,
                                        (LONG)OldValue) != (LONG)OldValue);
}


//* Random - Generate a pseudo random value between 0 and 2^32 - 1.
//
//  This routine is a quick and dirty psuedo random number generator.
//  It has the advantages of being fast and consuming very little
//  memory (for either code or data).  The random numbers it produces are
//  not of the best quality, however.  A much better generator could be
//  had if we were willing to use an extra 256 bytes of memory for data.
//
//  This routine uses the linear congruential method (see Knuth, Vol II),
//  with specific values for the multiplier and constant taken from
//  Numerical Recipes in C Second Edition by Press, et. al.
//
uint
Random(void)
{
    uint NewValue, OldValue;

    //
    // The algorithm is R = (aR + c) mod m, where R is the random number,
    // a is a magic multiplier, c is a constant, and the modulus m is the
    // maximum number of elements in the period.  We chose our m to be 2^32
    // in order to get the mod operation for free.
    //
    do {
        OldValue = RandomValue;
        NewValue = (1664525 * OldValue) + 1013904223;
    } while (InterlockedCompareExchange((PLONG)&RandomValue,
                                        (LONG)NewValue,
                                        (LONG)OldValue) != (LONG)OldValue);

    return NewValue;
}


//* RandomNumber
//
//  Returns a number randomly selected from a range.
//
uint
RandomNumber(uint Min, uint Max)
{
    uint Number;

    //
    // Note that the high bits of Random() are much more random
    // than the low bits.
    //
    Number = Max - Min; // Spread.
    Number = (uint)(((ULONGLONG)Random() * Number) >> 32); // Randomize spread.
    Number += Min;

    return Number;
}


//* CopyToBufferChain - Copy received packet to NDIS buffer chain.
//
//  Copies from a received packet to an NDIS buffer chain.
//  The received packet data comes in two flavors: if SrcPacket is
//  NULL, then SrcData is used. SrcOffset specifies an offset
//  into SrcPacket or SrcData.
//
//  Length limits the number of bytes copied. The number of bytes
//  copied may also be limited by the destination & source.
//
uint  // Returns: number of bytes copied.
CopyToBufferChain(
    PNDIS_BUFFER DstBuffer,
    uint DstOffset,
    PNDIS_PACKET SrcPacket,
    uint SrcOffset,
    uchar *SrcData,
    uint Length)
{
    PNDIS_BUFFER SrcBuffer;
    uchar *DstData;
    uint DstSize, SrcSize;
    uint BytesCopied, BytesToCopy;

    //
    // Skip DstOffset bytes in the destination buffer chain.
    // NB: DstBuffer might be NULL to begin with; that's legitimate.
    //
    for (;;) {
        if (DstBuffer == NULL)
            return 0;

        NdisQueryBufferSafe(DstBuffer, &DstData, &DstSize, LowPagePriority);
        if (DstData == NULL) {
            //
            // Couldn't map destination buffer into kernel address space.
            //
            return 0;
        }

        if (DstOffset < DstSize) {
            DstData += DstOffset;
            DstSize -= DstOffset;
            break;
        }

        DstOffset -= DstSize;
        NdisGetNextBuffer(DstBuffer, &DstBuffer);
    }

    if (SrcPacket != NULL) {
        //
        // Skip SrcOffset bytes into SrcPacket.
        // NB: SrcBuffer might be NULL to begin with; that's legitimate.
        //
        NdisQueryPacket(SrcPacket, NULL, NULL, &SrcBuffer, NULL);

        for (;;) {
            if (SrcBuffer == NULL)
                return 0;

            NdisQueryBuffer(SrcBuffer, &SrcData, &SrcSize);

            if (SrcOffset < SrcSize) {
                SrcData += SrcOffset;
                SrcSize -= SrcOffset;
                break;
            }

            SrcOffset -= SrcSize;
            NdisGetNextBuffer(SrcBuffer, &SrcBuffer);
        }
    } else {
        //
        // Using SrcData/SrcOffset instead of SrcPacket/SrcOffset.
        // In this case, we need not initialize SrcBuffer
        // because the copy loop below will never attempt
        // to advance to another SrcBuffer.
        //
        SrcSize = Length;
        SrcData += SrcOffset;
    }

    //
    // Perform the copy, advancing DstBuffer and SrcBuffer as needed.
    // Normally Length is initially non-zero, so no reason
    // to check Length first.
    //
    for (BytesCopied = 0;;) {

        BytesToCopy = MIN(MIN(Length, SrcSize), DstSize);
        RtlCopyMemory(DstData, SrcData, BytesToCopy);
        BytesCopied += BytesToCopy;

        Length -= BytesToCopy;
        if (Length == 0)
            break;  // All done.

        DstData += BytesToCopy;
        DstSize -= BytesToCopy;
        if (DstSize == 0) {
            //
            // We ran out of room in our current destination buffer.
            // Proceed to next buffer on the chain.
            //
            NdisGetNextBuffer(DstBuffer, &DstBuffer);
            if (DstBuffer == NULL)
                break;

            NdisQueryBuffer(DstBuffer, &DstData, &DstSize);
        }

        SrcData += BytesToCopy;
        SrcSize -= BytesToCopy;
        if (SrcSize == 0) {
            //
            // We ran out of data in our current source buffer.
            // Proceed to the next buffer on the chain.
            //
            NdisGetNextBuffer(SrcBuffer, &SrcBuffer);
            if (SrcBuffer == NULL)
                break;

            NdisQueryBuffer(SrcBuffer, &SrcData, &SrcSize);
        }
    }

    return BytesCopied;
}


//* CopyPacketToNdis - Copy from an IPv6Packet chain to an NDIS buffer.
//
//  This is the function we use to copy from a chain of IPv6Packets
//  to ONE NDIS buffer.  The caller specifies the source and
//  destination, a maximum size to copy, and an offset into the first
//  packet to start copying from.  We copy as much as possible up to the
//  size, and return the size copied.
//
//  Note that SrcOffset is relative to the beginning of the first packet in
//  the chain, and NOT the current 'Position' in that packet.
//
//  The source packet chain is not modified in any way by this routine.
//
uint  // Returns: Bytes copied.
CopyPacketToNdis(
    PNDIS_BUFFER DestBuf,  // Destination NDIS buffer chain.
    IPv6Packet *SrcPkt,    // Source packet chain.
    uint Size,             // Size in bytes to copy.
    uint DestOffset,       // Offset into dest buffer to start copying to.
    uint SrcOffset)        // Offset into packet chain to copy from.
{
    uint TotalBytesCopied = 0;  // Bytes we've copied so far.
    uint BytesCopied;           // Bytes copied out of each buffer.
    uint DestSize;              // Space left in destination.
    void *SrcData;              // Current source data pointer.
    uint SrcContig;             // Amount of Contiguous data from SrcData on.
    PNDIS_BUFFER SrcBuf;        // Current buffer in current packet.
    PNDIS_BUFFER TempBuf;       // Used to count through destination chain.
    uint PacketSize;            // Total size of current packet.
    NTSTATUS Status;


    ASSERT(SrcPkt != NULL);

    //
    // The destination buffer can be NULL - this is valid, if odd.
    //
    if (DestBuf == NULL)
        return 0;

    //
    // Limit our copy to the smaller of the requested amount and the
    // available space in the destination buffer chain.
    //
    TempBuf = DestBuf;
    DestSize = 0;

    do {
        DestSize += NdisBufferLength(TempBuf);
        TempBuf = NDIS_BUFFER_LINKAGE(TempBuf);
    } while (TempBuf);

    ASSERT(DestSize >= DestOffset);
    DestSize -= DestOffset;
    DestSize = MIN(DestSize, Size);

    //
    // First, skip SrcOffset bytes into the source packet chain.
    //
    if ((SrcOffset == SrcPkt->Position) && (Size <= SrcPkt->ContigSize)) {
        //
        // One common case is that we want to start from the current Position.
        // REVIEW: This case common enough to be worth this check?
        //
        SrcContig = SrcPkt->ContigSize;
        SrcData = SrcPkt->Data;
        SrcBuf = NULL;
        PacketSize = SrcPkt->TotalSize;
    } else {
        //
        // Otherwise step through packets and buffer regions until
        // we find the desired spot.
        //
        PacketSize = SrcPkt->Position + SrcPkt->TotalSize;
        while (SrcOffset >= PacketSize) {
            // Skip a whole packet.
            SrcOffset -= PacketSize;
            SrcPkt = SrcPkt->Next;
            ASSERT(SrcPkt != NULL);
            PacketSize = SrcPkt->Position + SrcPkt->TotalSize;
        }
        //
        // Found the right packet in the chain, now find desired buffer.
        //
        PacketSize -= SrcOffset;
        if (SrcPkt->NdisPacket == NULL) {
            //
            // This packet must be just a single contiguous region.
            // Finding the right spot is a simple matter of arithmetic.
            //
            SrcContig = PacketSize;
            SrcData = (uchar *)SrcPkt->FlatData + SrcOffset;
            SrcBuf = NULL;
        } else {
            uchar *BufAddr;
            uint BufLen;

            //
            // There may be multiple buffers comprising this packet.
            // Step through them until we arrive at the right spot.
            //
            SrcBuf = NdisFirstBuffer(SrcPkt->NdisPacket);
            NdisQueryBuffer(SrcBuf, &BufAddr, &BufLen);
            while (SrcOffset >= BufLen) {
                // Skip to the next buffer.
                SrcOffset -= BufLen;
                NdisGetNextBuffer(SrcBuf, &SrcBuf);
                ASSERT(SrcBuf != NULL);
                NdisQueryBuffer(SrcBuf, &BufAddr, &BufLen);
            }
            SrcContig = BufLen - SrcOffset;
            SrcData = BufAddr + BufLen - SrcContig;
        }
    }

    //
    // We're now at the point where we wish to start copying.
    //
    while (DestSize != 0) {
        uint BytesToCopy;

        BytesToCopy = MIN(DestSize, SrcContig);
        Status = TdiCopyBufferToMdl(SrcData, 0, BytesToCopy,
                                    DestBuf, DestOffset, &BytesCopied);
        if (!NT_SUCCESS(Status)) {
            break;
        }
        ASSERT(BytesCopied == BytesToCopy);
        TotalBytesCopied += BytesToCopy;

        if (BytesToCopy < DestSize) {
            //
            // Not done yet, we ran out of either source packet or buffer.
            // Get next one and fix up pointers/sizes for the next copy.
            //
            DestOffset += BytesToCopy;
            PacketSize -= BytesToCopy;
            if (PacketSize == 0) {
                // Get next packet on chain.
                SrcPkt = SrcPkt->Next;
                ASSERT(SrcPkt != NULL);
                PacketSize = SrcPkt->Position + SrcPkt->TotalSize;
                if (SrcPkt->NdisPacket == NULL) {
                    // Single contiguous region.
                    SrcData = (uchar *)SrcPkt->FlatData + SrcPkt->Position;
                    SrcContig = SrcPkt->TotalSize;
                } else {
                    // Potentially multiple buffers.
                    SrcBuf = NdisFirstBuffer(SrcPkt->NdisPacket);
                    NdisQueryBuffer(SrcBuf, &SrcData, &SrcContig);
                }
            } else {
                // Get next buffer in packet.
                ASSERT(SrcBuf != NULL);
                NdisGetNextBuffer(SrcBuf, &SrcBuf);
                ASSERT(SrcBuf != NULL);
                NdisQueryBuffer(SrcBuf, &SrcData, &SrcContig);
            }
        }
        DestSize -= BytesToCopy;
    }

    return TotalBytesCopied;
}


//* CopyPacketToBuffer - Copy from an IPv6Packet chain to a flat buffer.
//
//  Called during receive processing to copy from an IPv6Packet chain to a
//  flat buffer.  We skip SrcOffset bytes into the source chain, and then
//  copy Size bytes.
//
//  Note that SrcOffset is relative to the beginning of the packet, NOT
//  the current 'Position'.
//
//  The source packet chain is not modified in any way by this routine.
//
void  // Returns: Nothing.
CopyPacketToBuffer(
    uchar *DestBuf,      // Destination buffer (unstructured memory).
    IPv6Packet *SrcPkt,  // Source packet chain.
    uint Size,           // Size in bytes to copy.
    uint SrcOffset)      // Offset in SrcPkt to start copying from.
{
    uint SrcContig;
    void *SrcData;
    PNDIS_BUFFER SrcBuf;
    uint PacketSize;

#if DBG
    IPv6Packet *TempPkt;
    uint TempSize;
#endif

    ASSERT(DestBuf != NULL);
    ASSERT(SrcPkt != NULL);

#if DBG
    //
    // In debug versions check to make sure we're copying a reasonable size
    // and from a reasonable offset.
    //
    TempPkt = SrcPkt;
    TempSize = TempPkt->Position + TempPkt->TotalSize;
    TempPkt = TempPkt->Next;
    while (TempPkt != NULL) {
        TempSize += TempPkt->TotalSize;
        TempPkt = TempPkt->Next;
    }

    ASSERT(SrcOffset <= TempSize);
    ASSERT((SrcOffset + Size) <= TempSize);
#endif

    //
    // First, skip SrcOffset bytes into the source packet chain.
    //
    if ((SrcOffset == SrcPkt->Position) && (Size <= SrcPkt->ContigSize)) {
        //
        // One common case is that we want to start from the current Position.
        // REVIEW: This case common enough to be worth this check?
        //
        SrcContig = SrcPkt->ContigSize;
        SrcData = SrcPkt->Data;
        SrcBuf = NULL;
        PacketSize = SrcPkt->TotalSize;
    } else {
        //
        // Otherwise step through packets and buffer regions until
        // we find the desired spot.
        //
        PacketSize = SrcPkt->Position + SrcPkt->TotalSize;
        while (SrcOffset >= PacketSize) {
            // Skip a whole packet.
            SrcOffset -= PacketSize;
            SrcPkt = SrcPkt->Next;
            ASSERT(SrcPkt != NULL);
            PacketSize = SrcPkt->Position + SrcPkt->TotalSize;
        }
        //
        // Found the right packet in the chain, now find desired buffer.
        //
        PacketSize -= SrcOffset;
        if (SrcPkt->NdisPacket == NULL) {
            //
            // This packet must be just a single contiguous region.
            // Finding the right spot is a simple matter of arithmetic.
            //
            SrcContig = PacketSize;
            SrcData = (uchar *)SrcPkt->FlatData + SrcOffset;
            SrcBuf = NULL;
        } else {
            uchar *BufAddr;
            uint BufLen;

            //
            // There may be multiple buffers comprising this packet.
            // Step through them until we arrive at the right spot.
            //
            SrcBuf = NdisFirstBuffer(SrcPkt->NdisPacket);
            NdisQueryBuffer(SrcBuf, &BufAddr, &BufLen);
            while (SrcOffset >= BufLen) {
                // Skip to the next buffer.
                SrcOffset -= BufLen;
                NdisGetNextBuffer(SrcBuf, &SrcBuf);
                ASSERT(SrcBuf != NULL);
                NdisQueryBuffer(SrcBuf, &BufAddr, &BufLen);
            }
            SrcContig = BufLen - SrcOffset;
            SrcData = BufAddr + BufLen - SrcContig;
        }
    }

    //
    // We're now at the point where we wish to start copying.
    //
    while (Size != 0) {
        uint BytesToCopy;

        BytesToCopy = MIN(Size, SrcContig);
        RtlCopyMemory(DestBuf, (uchar *)SrcData, BytesToCopy);

        if (BytesToCopy < Size) {
            //
            // Not done yet, we ran out of either source packet or buffer.
            // Get next one and fix up pointers/sizes for the next copy.
            //
            DestBuf += BytesToCopy;
            PacketSize -= BytesToCopy;
            if (PacketSize == 0) {
                // Get next packet on chain.
                SrcPkt = SrcPkt->Next;
                ASSERT(SrcPkt != NULL);
                PacketSize = SrcPkt->Position + SrcPkt->TotalSize;
                if (SrcPkt->NdisPacket == NULL) {
                    // Single contiguous region.
                    SrcData = (uchar *)SrcPkt->FlatData + SrcPkt->Position;
                    SrcContig = SrcPkt->TotalSize;
                } else {
                    // Potentially multiple buffers.
                    SrcBuf = NdisFirstBuffer(SrcPkt->NdisPacket);
                    NdisQueryBuffer(SrcBuf, &SrcData, &SrcContig);
                }
            } else {
                // Get next buffer in packet.
                ASSERT(SrcBuf != NULL);
                NdisGetNextBuffer(SrcBuf, &SrcBuf);
                ASSERT(SrcBuf != NULL);
                NdisQueryBuffer(SrcBuf, &SrcData, &SrcContig);
            }
        }
        Size -= BytesToCopy;
    }
}

//* CopyToNdisSafe - Copy a flat buffer to an NDIS_BUFFER chain.
//
//  A utility function to copy a flat buffer to an NDIS buffer chain. We
//  assume that the NDIS_BUFFER chain is big enough to hold the copy amount;
//  in a debug build we'll assert if this isn't true. We return a pointer
//  to the buffer where we stopped copying, and an offset into that buffer.
//  This is useful for copying in pieces into the chain.
//
//  Input:  DestBuf     - Destination NDIS_BUFFER chain.
//          pNextBuf    - Pointer to next buffer in chain to copy into.
//          SrcBuf      - Src flat buffer.
//          Size        - Size in bytes to copy.
//          StartOffset - Pointer to start of offset into first buffer in
//                          chain. Filled in on return with the offset to
//                          copy into next.
//
//  Returns: TRUE  - Successfully copied flat buffer into NDIS_BUFFER chain.
//           FALSE - Failed to copy entire flat buffer.
//
int
CopyToNdisSafe(PNDIS_BUFFER DestBuf, PNDIS_BUFFER * ppNextBuf,
               uchar * SrcBuf, uint Size, uint * StartOffset)
{
    uint CopySize;
    uchar *DestPtr;
    uint DestSize;
    uint Offset = *StartOffset;
    uchar *VirtualAddress;
    uint Length;

    ASSERT(DestBuf != NULL);
    ASSERT(SrcBuf != NULL);

    NdisQueryBufferSafe(DestBuf, &VirtualAddress, &Length,
                        LowPagePriority);
    if (VirtualAddress == NULL)
        return FALSE;

    ASSERT(Length >= Offset);
    DestPtr = VirtualAddress + Offset;
    DestSize = Length - Offset;

    for (;;) {
        CopySize = MIN(Size, DestSize);
        RtlCopyMemory(DestPtr, SrcBuf, CopySize);

        DestPtr += CopySize;
        SrcBuf += CopySize;

        if ((Size -= CopySize) == 0)
            break;

        if ((DestSize -= CopySize) == 0) {
            DestBuf = NDIS_BUFFER_LINKAGE(DestBuf);
            ASSERT(DestBuf != NULL);

            NdisQueryBufferSafe(DestBuf, &VirtualAddress, &Length,
                                LowPagePriority);
            if (VirtualAddress == NULL)
                return FALSE;

            DestPtr = VirtualAddress;
            DestSize = Length;
        }
    }

    *StartOffset = (uint) (DestPtr - VirtualAddress);

    if (ppNextBuf)
        *ppNextBuf = DestBuf;
    return TRUE;
}


//* CopyFlatToNdis - Copy a flat buffer to an NDIS_BUFFER chain.
//
//  A utility function to copy a flat buffer to an NDIS buffer chain.  We
//  assume that the NDIS_BUFFER chain is big enough to hold the copy amount;
//  in a debug build we'll debugcheck if this isn't true.  We return a pointer
//  to the buffer where we stopped copying, and an offset into that buffer.
//  This is useful for copying in pieces into the chain.
//
PNDIS_BUFFER  // Returns: Pointer to next buffer in chain to copy into.
CopyFlatToNdis(
    PNDIS_BUFFER DestBuf,  // Destination NDIS buffer chain.
    uchar *SrcBuf,         // Source buffer (unstructured memory).
    uint Size,             // Size in bytes to copy.
    uint *StartOffset,     // Pointer to Offset info first buffer in chain.
                           // Filled on return with offset to copy into next.
    uint *BytesCopied)     // Location into which to return # of bytes copied.
{
    NTSTATUS Status = 0;

    *BytesCopied = 0;

    Status = TdiCopyBufferToMdl(SrcBuf, 0, Size, DestBuf, *StartOffset,
                                BytesCopied);

    *StartOffset += *BytesCopied;

    //
    // Always return the first buffer, since the TdiCopy function handles
    // finding the appropriate buffer based on offset.
    //
    return(DestBuf);
}


//* CopyNdisToFlat - Copy an NDIS_BUFFER chain to a flat buffer.
//
//  Copy (a portion of) an NDIS buffer chain to a flat buffer.
//
//  Returns TRUE if the copy succeeded and FALSE if it failed
//  because an NDIS buffer could not be mapped. If the copy succeeded,
//  returns the Next buffer/offset, for subsequent calls.
//
int
CopyNdisToFlat(
    void *DstData,
    PNDIS_BUFFER SrcBuffer,
    uint SrcOffset,
    uint Length,
    PNDIS_BUFFER *NextBuffer,
    uint *NextOffset)
{
    void *SrcData;
    uint SrcSize;
    uint Bytes;

    for (;;) {
        NdisQueryBufferSafe(SrcBuffer, &SrcData, &SrcSize, LowPagePriority);
        if (SrcSize < SrcOffset) {
            SrcOffset -= SrcSize;
            NdisGetNextBuffer(SrcBuffer, &SrcBuffer);
            continue;
        }

        if (SrcData == NULL)
            return FALSE;

        Bytes = SrcSize - SrcOffset;
        if (Bytes > Length)
            Bytes = Length;

        RtlCopyMemory(DstData, (uchar *)SrcData + SrcOffset, Bytes);

        (uchar *)DstData += Bytes;
        SrcOffset += Bytes;
        Length -= Bytes;

        if (Length == 0)
            break;

        NdisGetNextBuffer(SrcBuffer, &SrcBuffer);
        SrcOffset = 0;
    }

    *NextBuffer = SrcBuffer;
    *NextOffset = SrcOffset;
    return TRUE;
}


//
// Checksum support.
// On NT, there are architecture-specific assembly routines for the core
// calculation.
//

//* ChecksumPacket - Calculate the Internet checksum of a packet.
//
//  Calculates the checksum of packet data. The data may be supplied
//  either with the Packet/Offset arguments, or (if Packet is NULL)
//  the Data/Offset arguments. In either case, Length specifies how much
//  data to checksum.
//
//  The Packet is assumed to contain (at least) Offset + Length bytes.
//
//  Also calculates and adds-in the pseudo-header checksum,
//  using Source, Dest, Length, and NextHeader.
//
//  Returns 0 for failure, when an NDIS buffer can not be mapped
//  into kernel address space due to resource shortages.
//
ushort
ChecksumPacket(
    PNDIS_PACKET Packet,    // Packet with data to checksum.
    uint Offset,            // Offset into packet where data starts.
    uchar *Data,            // If Packet is NULL, data to checksum.
    uint Length,            // Length of packet data.
    const IPv6Addr *Source, // Source address.
    const IPv6Addr *Dest,   // Destination address.
    uchar NextHeader)       // Protocol type for pseudo-header.
{
    PNDIS_BUFFER Buffer;
    uint Checksum;
    uint PayloadLength;
    uint Size;
    uint TotalSummed;

    //
    // Start with the pseudo-header.
    //
    Checksum = Cksum(Source, sizeof *Source) + Cksum(Dest, sizeof *Dest);
    PayloadLength = net_long(Length);
    Checksum += (PayloadLength >> 16) + (PayloadLength & 0xffff);
    Checksum += (NextHeader << 8);

    if (Packet == NULL) {
        //
        // We do not have to initialize Buffer.
        // The checksum loop below will exit before trying to use it.
        //
        Size = Length;
        Data += Offset;
    } else {
        //
        // Skip over Offset bytes in the packet.
        //

        Buffer = NdisFirstBuffer(Packet);
        for (;;) {
            Size = NdisBufferLength(Buffer);

            //
            // There is a boundary case here: the Packet contains
            // exactly Offset bytes total, and Length is zero.
            // Checking Offset <= Size instead of Offset < Size
            // makes this work.
            //
            if (Offset <= Size) {
                Data = NdisBufferVirtualAddressSafe(Buffer, LowPagePriority);
                if (Data == NULL)
                    return 0;

                Data += Offset;
                Size -= Offset;
                break;
            }

            Offset -= Size;
            NdisGetNextBuffer(Buffer, &Buffer);
            ASSERT(Buffer != NULL); // Caller ensures this.
        }
    }
    for (TotalSummed = 0;;) {
        ushort Temp;

        //
        // Size might be bigger than we need,
        // if there is "extra" data in the packet.
        //
        if (Size > Length)
            Size = Length;

        Temp = Cksum(Data, Size);
        if (TotalSummed & 1) {
            // We're at an odd offset into the logical buffer,
            // so we need to swap the bytes that Cksum returns.
            Checksum += (Temp >> 8) + ((Temp & 0xff) << 8);
        } else {
            Checksum += Temp;
        }
        TotalSummed += Size;

        Length -= Size;
        if (Length == 0)
            break;
        // Buffer is always initialized if we reach here.
        NdisGetNextBuffer(Buffer, &Buffer);
        NdisQueryBufferSafe(Buffer, &Data, &Size, LowPagePriority);
        if (Data == NULL)
            return 0;
    }

    //
    // Wrap in the carries to reduce Checksum to 16 bits.
    // (Twice is sufficient because it can only overflow once.)
    //
    Checksum = (Checksum >> 16) + (Checksum & 0xffff);
    Checksum += (Checksum >> 16);

    //
    // Take ones-complement and replace 0 with 0xffff.
    //
    Checksum = (ushort) ~Checksum;
    if (Checksum == 0)
        Checksum = 0xffff;

    return (ushort) Checksum;
}

//* ConvertSecondsToTicks
//
//  Convert seconds to timer ticks.
//  A value of INFINITE_LIFETIME (0xffffffff) indicates infinity,
//  for both ticks and seconds.
//
uint
ConvertSecondsToTicks(uint Seconds)
{
    uint Ticks;

    Ticks = Seconds * IPv6_TICKS_SECOND;
    if (Ticks / IPv6_TICKS_SECOND != Seconds)
        Ticks = INFINITE_LIFETIME; // Overflow.

    return Ticks;
}

//* ConvertTicksToSeconds
//
//  Convert timer ticks to seconds.
//  A value of INFINITE_LIFETIME (0xffffffff) indicates infinity,
//  for both ticks and seconds.
//
uint
ConvertTicksToSeconds(uint Ticks)
{
    uint Seconds;

    if (Ticks == INFINITE_LIFETIME)
        Seconds = INFINITE_LIFETIME;
    else
        Seconds = Ticks / IPv6_TICKS_SECOND;

    return Seconds;
}

//* ConvertMillisToTicks
//
//  Convert milliseconds to timer ticks.
//
uint
ConvertMillisToTicks(uint Millis)
{
    uint Ticks;

    //
    // Use 64-bit arithmetic to guard against intermediate overlow.
    //
    Ticks = (uint) (((unsigned __int64) Millis * IPv6_TICKS_SECOND) / 1000);

    //
    // If the number of millis is non-zero,
    // then have at least one tick.
    //
    if (Ticks == 0 && Millis != 0)
        Ticks = 1;

    return Ticks;
}

//* IPv6Timeout - Perform various housekeeping duties periodically.
//
//  Neighbor discovery, fragment reassembly, ICMP ping, etc. all have
//  time-dependent parts.  Check for timer expiration here.
//
void
IPv6Timeout(
    PKDPC MyDpcObject,  // The DPC object describing this routine.
    void *Context,      // The argument we asked to be called with.
    void *Unused1,
    void *Unused2)
{
    UNREFERENCED_PARAMETER(MyDpcObject);
    UNREFERENCED_PARAMETER(Context);
    UNREFERENCED_PARAMETER(Unused1);
    UNREFERENCED_PARAMETER(Unused2);

    //
    // Atomically increment our tick count.
    //
    InterlockedIncrement((LONG *)&IPv6TickCount);

    // 
    // Process all multicast groups with timers running.  Timers are used to
    // response to membership queries sent to us by the first-hop router.
    //
    // We call MLDTimeout *before* InterfaceTimeout and NetTableTimeout.
    // so that when an interface is first created and a link-local address
    // is assigned, the initial MLD Report for the solicited-node multicast
    // address gets sent *before* the Neighbor Solicit for DAD.
    // Similarly, we join the all-routers link-local multicast group
    // before sending our first RA from an advertising interface.
    //
    if (QueryList != NULL)
        MLDTimeout();

    //
    // Handle per-interface timeouts.
    //
    InterfaceTimeout();

    //
    // Handle per-NTE timeouts.
    //
    NetTableTimeout();

    //
    // Handle routing table timeouts.
    //
    RouteTableTimeout();

    //
    // If there's a possibility we have one or more outstanding echo requests,
    // call out to ICMPv6 to handle them.  Note that since we don't grab the
    // lock here, there may be none by the time we get there.  This just saves
    // us from always having to call out.
    //
    if (ICMPv6OutstandingEchos != NULL) {
        //
        // Echo requests outstanding.
        //
        ICMPv6EchoTimeout();
    }

    //
    // If we might have active reassembly records,
    // call out to handle timeout processing for them.
    //
    if (ReassemblyList.First != SentinelReassembly) {
        ReassemblyTimeout();
    }

    //
    // Check for expired binding cache entries.
    //
    if (BindingCache.First != SentinelBCE)
        BindingCacheTimeout();

    //
    // Check for expired site prefixes.
    //
    if (SitePrefixTable != NULL)
        SitePrefixTimeout();
}

//* AdjustPacketBuffer
//
//  Takes an NDIS Packet that has some spare bytes available
//  at the beginning and adjusts the size of that available space.
//
//  When we allocate packets, we often do not know a priori on which
//  link the packets will go out.  However it is much more efficient
//  to allocate space for the link-level header along with the rest
//  of the packet.  Hence we leave space for the maximum link-level header,
//  and each individual link layer uses AdjustPacketBuffer to shrink
//  that space to the size that it really needs.
//
//  AdjustPacketBuffer is needed because the sending calls (in both
//  the NDIS and TDI interfaces) do not allow the caller to specify
//  an offset of data to skip over.
//
//  Note that this code is NT-specific, because it knows about the
//  internal fields of NDIS_BUFFER structures.
//
void *
AdjustPacketBuffer(
    PNDIS_PACKET Packet,  // Packet to adjust.
    uint SpaceAvailable,  // Extra space available at start of first buffer.
    uint SpaceNeeded)     // Amount of space we need for the header.
{
    PMDL Buffer;
    uint Adjust;

    // Get first buffer on packet chain.
    NdisQueryPacket(Packet, NULL, NULL, &Buffer, NULL);

    //
    // The remaining space in the packet should all be in the first buffer.
    //
    ASSERT(SpaceAvailable <= Buffer->ByteCount);

    Adjust = SpaceAvailable - SpaceNeeded;
    if (Adjust == 0) {
        //
        // There is exactly the right amount of space left.
        // This is the common case.
        //
    } else if ((int)Adjust > 0) {
        //
        // There is too much space left.
        // Because NdisSend doesn't have an Offset argument,
        // we need to temporarily "shrink" the buffer.
        //
        (uchar *)Buffer->MappedSystemVa += Adjust;
        Buffer->ByteCount -= Adjust;
        Buffer->ByteOffset += Adjust;

        if (Buffer->ByteOffset >= PAGE_SIZE) {
            PFN_NUMBER FirstPage;

            //
            // Need to "remove" the first physical page
            // by shifting the array up one page.
            // Save it at the end of the array.
            //
            FirstPage = ((PPFN_NUMBER)(Buffer + 1))[0];
            RtlMoveMemory(&((PPFN_NUMBER)(Buffer + 1))[0],
                          &((PPFN_NUMBER)(Buffer + 1))[1],
                          Buffer->Size - sizeof *Buffer - sizeof(PFN_NUMBER));
            ((PPFN_NUMBER)((uchar *)Buffer + Buffer->Size))[-1] = FirstPage;

            (uchar *)Buffer->StartVa += PAGE_SIZE;
            Buffer->ByteOffset -= PAGE_SIZE;
        }
    } else { // Adjust < 0
        //
        // Not enough space.
        // Shouldn't happen in the normal send path.
        // REVIEW: This is a potential problem when forwarding packets
        // from an interface with a short link-level header
        // to an interface with a longer link-level header.
        // Should the forwarding code take care of this?
        //
        ASSERTMSG("AdjustPacketBuffer: Adjust < 0", FALSE);
    }

    //
    // Save away the adjustment for the completion callback,
    // which needs to undo our work with UndoAdjustPacketBuffer.
    //
    PC(Packet)->pc_adjust = Adjust;

    //
    // Return a pointer to the buffer.
    //
    return Buffer->MappedSystemVa;
}

//* UndoAdjustPacketBuffer
//
//  Undo the effects of AdjustPacketBuffer.
//
//  Note that this code is NT-specific, because it knows about the
//  internal fields of NDIS_BUFFER structures.
//
void
UndoAdjustPacketBuffer(
    PNDIS_PACKET Packet)  // Packet we may or may not have previously adjusted.
{
    uint Adjust;

    Adjust = PC(Packet)->pc_adjust;
    if (Adjust != 0) {
        PMDL Buffer;

        //
        // We need to undo the adjustment made in AdjustPacketBuffer.
        // This may including shifting the array of page info.
        //

        // Get first buffer on packet chain.
        NdisQueryPacket(Packet, NULL, NULL, &Buffer, NULL);

        if (Buffer->ByteOffset < Adjust) {
            PFN_NUMBER FirstPage;

            (uchar *)Buffer->StartVa -= PAGE_SIZE;
            Buffer->ByteOffset += PAGE_SIZE;

            FirstPage = ((PPFN_NUMBER)((uchar *)Buffer + Buffer->Size))[-1];
            RtlMoveMemory(&((PPFN_NUMBER)(Buffer + 1))[1],
                          &((PPFN_NUMBER)(Buffer + 1))[0],
                          Buffer->Size - sizeof *Buffer - sizeof(PFN_NUMBER));
            ((PPFN_NUMBER)(Buffer + 1))[0] = FirstPage;
        }

        (uchar *)Buffer->MappedSystemVa -= Adjust;
        Buffer->ByteCount += Adjust;
        Buffer->ByteOffset -= Adjust;
    }
}

//* CreateSolicitedNodeMulticastAddress
//
//  Given a unicast or anycast address, creates the corresponding
//  solicited-node multicast address.
//
void
CreateSolicitedNodeMulticastAddress(
    const IPv6Addr *Addr,
    IPv6Addr *MCastAddr)
{
    RtlZeroMemory(MCastAddr, sizeof *MCastAddr);
    MCastAddr->s6_bytes[0] = 0xff;
    MCastAddr->s6_bytes[1] = ADE_LINK_LOCAL;
    MCastAddr->s6_bytes[11] = 0x01;
    MCastAddr->s6_bytes[12] = 0xff;
    MCastAddr->s6_bytes[13] = Addr->s6_bytes[13];
    MCastAddr->s6_bytes[14] = Addr->s6_bytes[14];
    MCastAddr->s6_bytes[15] = Addr->s6_bytes[15];
}

//* IP6_ADDR_LTEQ
//
//  Is the first address <= the second address,
//  in a lexicographic ordering?
//
int
IP6_ADDR_LTEQ(const IPv6Addr *A, const IPv6Addr *B)
{
    uint i;

    for (i = 0; i < 16; i++) {
        if (A->s6_bytes[i] < B->s6_bytes[i])
            return TRUE;
        else if (A->s6_bytes[i] > B->s6_bytes[i])
            return FALSE;
    }

    return TRUE; // They are equal.
}

//* IsV4Compatible
//
//  Is this a v4-compatible address?
//
//  Note that the upper 8 bits of an IPv4 address are not allowed
//  to be zero.  If all 104 upper bits of an IPv6 address are zero,
//  it's potentially a valid native IPv6 address (e.g. loopback),
//  NOT a v4-compatible address.
//
int
IsV4Compatible(const IPv6Addr *Addr)
{
    return ((Addr->s6_words[0] == 0) &&
            (Addr->s6_words[1] == 0) &&
            (Addr->s6_words[2] == 0) &&
            (Addr->s6_words[3] == 0) &&
            (Addr->s6_words[4] == 0) &&
            (Addr->s6_words[5] == 0) &&
            (Addr->s6_bytes[12] != 0));
}

//* CreateV4Compatible
//
//  Create a v4-compatible address.
//
void
CreateV4Compatible(IPv6Addr *Addr, IPAddr V4Addr)
{
    Addr->s6_words[0] = 0;
    Addr->s6_words[1] = 0;
    Addr->s6_words[2] = 0;
    Addr->s6_words[3] = 0;
    Addr->s6_words[4] = 0;
    Addr->s6_words[5] = 0;
    * (IPAddr UNALIGNED *) &Addr->s6_words[6] = V4Addr;
}

//* IsV4Mapped
//
//  Is this a v4-mapped address?
//
int
IsV4Mapped(const IPv6Addr *Addr)
{
    return ((Addr->s6_words[0] == 0) &&
            (Addr->s6_words[1] == 0) &&
            (Addr->s6_words[2] == 0) &&
            (Addr->s6_words[3] == 0) &&
            (Addr->s6_words[4] == 0) &&
            (Addr->s6_words[5] == 0xffff));
}

//* CreateV4Mapped
//
//  Create a v4-mapped address.
//
void
CreateV4Mapped(IPv6Addr *Addr, IPAddr V4Addr)
{
    Addr->s6_words[0] = 0;
    Addr->s6_words[1] = 0;
    Addr->s6_words[2] = 0;
    Addr->s6_words[3] = 0;
    Addr->s6_words[4] = 0;
    Addr->s6_words[5] = 0xffff;
    * (IPAddr UNALIGNED *) &Addr->s6_words[6] = V4Addr;
}

//* IsSolicitedNodeMulticast
//
//  Is this a solicited-node multicast address?
//  Checks very strictly for the proper format.
//  For example scope values smaller than 2 are not allowed.
//
int
IsSolicitedNodeMulticast(const IPv6Addr *Addr)
{
    return ((Addr->s6_bytes[0] == 0xff) &&
            (Addr->s6_bytes[1] == ADE_LINK_LOCAL) &&
            (Addr->s6_words[1] == 0) &&
            (Addr->s6_words[2] == 0) &&
            (Addr->s6_words[3] == 0) &&
            (Addr->s6_words[4] == 0) &&
            (Addr->s6_bytes[10] == 0) &&
            (Addr->s6_bytes[11] == 0x01) &&
            (Addr->s6_bytes[12] == 0xff));
}

//* IsEUI64Address
//
//  Does the address have a format prefix
//  that indicates it uses EUI-64 interface identifiers?
//
int
IsEUI64Address(const IPv6Addr *Addr)
{
    //
    // Format prefixes 001 through 111, except for multicast.
    //
    return (((Addr->s6_bytes[0] & 0xe0) != 0) &&
            !IsMulticast(Addr));
}

//* IsSubnetRouterAnycast
//
//  Is this the subnet router anycast address?
//  See RFC 2373.
//
int
IsSubnetRouterAnycast(const IPv6Addr *Addr)
{
    return (IsEUI64Address(Addr) &&
            (Addr->s6_words[4] == 0) &&
            (Addr->s6_words[5] == 0) &&
            (Addr->s6_words[6] == 0) &&
            (Addr->s6_words[7] == 0));
}

//* IsSubnetReservedAnycast
//
//  Is this a subnet reserved anycast address?
//  See RFC 2526. It talks about non-EUI-64
//  addresses as well, but IMHO that part
//  of the RFC doesn't make sense. For example,
//  it shouldn't apply to multicast or v4-compatible
//  addresses.
//
int
IsSubnetReservedAnycast(const IPv6Addr *Addr)
{
    return (IsEUI64Address(Addr) &&
            (Addr->s6_words[4] == 0xfffd) &&
            (Addr->s6_words[5] == 0xffff) &&
            (Addr->s6_words[6] == 0xffff) &&
            ((Addr->s6_words[7] & 0x80ff) == 0x80ff));
}

//* IsKnownAnycast
//
//  As best we can tell from simple inspection,
//  is this an anycast address?
//
int
IsKnownAnycast(const IPv6Addr *Addr)
{
    return IsSubnetRouterAnycast(Addr) || IsSubnetReservedAnycast(Addr);
}

//* IsInvalidSourceAddress
//
//  Is this address illegal to use as a source address?
//  We currently flag IPv6 multicast, and embedded IPv4 multicast,
//  broadcast, loopback and unspecified as invalid.
//
//  Note that this function doesn't attempt to identify anycast addresses
//  in order to flag them as invalid.  Whether or not to allow them to
//  be valid source addresses has been a matter of some debate in the
//  working group.  We let them pass since we can't tell them all by
//  inspection and we don't see any real problems with accepting them.
//
int
IsInvalidSourceAddress(const IPv6Addr *Addr)
{
    IPAddr V4Addr;

    if (IsMulticast(Addr))
        return TRUE;

    if (IsISATAP(Addr) ||                          // ISATAP
        (((Addr->s6_words[0] == 0) && (Addr->s6_words[1] == 0) &&
          (Addr->s6_words[2] == 0) && (Addr->s6_words[3] == 0)) &&
         ((Addr->s6_words[4] == 0) && (Addr->s6_words[5] == 0) &&
          ((Addr->s6_words[6] & 0x00ff) != 0)) ||  // v4-compatible
         ((Addr->s6_words[4] == 0) &&
          (Addr->s6_words[5] == 0xffff)) ||        // v4-mapped
         ((Addr->s6_words[4] == 0xffff) &&
          (Addr->s6_words[5] == 0)))) {            // v4-translated

        V4Addr = ExtractV4Address(Addr);

    } else if (Is6to4(Addr)) {

        V4Addr = Extract6to4Address(Addr);

    } else {        
        //
        // It's not an IPv6 multicast address, nor does it contain
        // an embedded IPv4 address of some sort, so don't consider
        // it invalid.
        //
        return FALSE;
    }

    //
    // Check embedded IPv4 address for invalid types.
    //
    return (IsV4Multicast(V4Addr) || IsV4Broadcast(V4Addr) ||
            IsV4Loopback(V4Addr) || IsV4Unspecified(V4Addr));
}

//* IsNotManualAddress
//
//  Should this address NOT be manually assigned as an address?
//
int
IsNotManualAddress(const IPv6Addr *Addr)
{
    return (IsMulticast(Addr) ||
            IsUnspecified(Addr) ||
            IsLoopback(Addr) ||
            (IsV4Compatible(Addr) &&
             (V4AddressScope(ExtractV4Address(Addr)) != ADE_GLOBAL)) ||
            (Is6to4(Addr) &&
             (V4AddressScope(Extract6to4Address(Addr)) != ADE_GLOBAL)));
}

//* V4AddressScope
//
//  Determines the scope of an IPv4 address.
//  See RFC 1918.
//
ushort
V4AddressScope(IPAddr Addr)
{
    if ((Addr & 0x0000FFFF) == 0x0000FEA9) // 169.254/16 - auto-configured
        return ADE_LINK_LOCAL;
    else if ((Addr & 0x000000FF) == 0x0000000A) // 10/8 - private
        return ADE_SITE_LOCAL;
    else if ((Addr & 0x0000F0FF) == 0x000010AC) // 172.16/12 - private
        return ADE_SITE_LOCAL;
    else if ((Addr & 0x0000FFFF) == 0x0000A8C0) // 192.168/16 - private
        return ADE_SITE_LOCAL;
    else if ((Addr & 0x000000FF) == 0x0000007F) // 127/8 - loopback
        return ADE_LINK_LOCAL;
    else
        return ADE_GLOBAL;
}

//* UnicastAddressScope
//
//  Examines a unicast address and determines its scope.
//
//  Note that v4-compatible and 6to4 addresses
//  are deemed to have global scope. They should
//  not be derived from RFC 1918 IPv4 addresses.
//  But even if they are, we will treat the IPv6
//  addresses as global.
//
ushort
UnicastAddressScope(const IPv6Addr *Addr)
{
    if (IsLinkLocal(Addr))
        return ADE_LINK_LOCAL;
    else if (IsSiteLocal(Addr))
        return ADE_SITE_LOCAL;
    else if (IsLoopback(Addr))
        return ADE_LINK_LOCAL;
    else
        return ADE_GLOBAL;
}

//* AddressScope
//
//  Examines an address and determines its scope.
//
ushort
AddressScope(const IPv6Addr *Addr)
{
    if (IsMulticast(Addr))
        return MulticastAddressScope(Addr);
    else
        return UnicastAddressScope(Addr);
}

//* DetermineScopeId
//
//  Given an address and an associated interface, determine
//  the appropriate value for the scope identifier.
//
//  DetermineScopeId calculates a "user-level" ScopeId,
//  meaning that loopback and global addresses
//  get special treatment. Therefore, DetermineScopeId
//  is not appropriate for general network-layer use.
//  See also RouteToDestination and FindNetworkWithAddress.
//
//  Returns the ScopeId.
//
uint
DetermineScopeId(const IPv6Addr *Addr, Interface *IF)
{
    ushort Scope;

    if (IsLoopback(Addr) && (IF == LoopInterface))
        return 0;

    Scope = AddressScope(Addr);
    if (Scope == ADE_GLOBAL)
        return 0;

    return IF->ZoneIndices[Scope];
}

//* HasPrefix - Does an address have the given prefix?
//
int
HasPrefix(const IPv6Addr *Addr, const IPv6Addr *Prefix, uint PrefixLength)
{
    const uchar *AddrBytes = Addr->s6_bytes;
    const uchar *PrefixBytes = Prefix->s6_bytes;

    //
    // Check that initial integral bytes match.
    //
    while (PrefixLength > 8) {
        if (*AddrBytes++ != *PrefixBytes++)
            return FALSE;
        PrefixLength -= 8;
    }

    //
    // Check any remaining bits.
    // Note that if PrefixLength is zero now, we should not
    // dereference AddrBytes/PrefixBytes.
    //
    if ((PrefixLength > 0) &&
        ((*AddrBytes >> (8 - PrefixLength)) !=
         (*PrefixBytes >> (8 - PrefixLength))))
        return FALSE;

    return TRUE;
}

//* CopyPrefix
//
//  Copy an address prefix, zeroing the remaining bits
//  in the destination address.
//
void
CopyPrefix(IPv6Addr *Addr, const IPv6Addr *Prefix, uint PrefixLength)
{
    uint PLBytes, PLRemainderBits, Loop;

    PLBytes = PrefixLength / 8;
    PLRemainderBits = PrefixLength % 8;
    for (Loop = 0; Loop < sizeof(IPv6Addr); Loop++) {
        if (Loop < PLBytes)
            Addr->s6_bytes[Loop] = Prefix->s6_bytes[Loop];
        else
            Addr->s6_bytes[Loop] = 0;
    }
    if (PLRemainderBits) {
        Addr->s6_bytes[PLBytes] = Prefix->s6_bytes[PLBytes] &
            (0xff << (8 - PLRemainderBits));
    }
}

//* CommonPrefixLength
//
//  Calculate the length of the longest prefix common
//  to the two addresses.
//
uint
CommonPrefixLength(const IPv6Addr *Addr, const IPv6Addr *Addr2)
{
    int i, j;

    //
    // Find first non-matching byte.
    //
    for (i = 0; ; i++) {
        if (i == sizeof(IPv6Addr))
            return 8 * i;

        if (Addr->s6_bytes[i] != Addr2->s6_bytes[i])
            break;
    }

    //
    // Find first non-matching bit (there must be one).
    //
    for (j = 0; ; j++) {
        uint Mask = 1 << (7 - j);

        if ((Addr->s6_bytes[i] & Mask) != (Addr2->s6_bytes[i] & Mask))
            break;
    }

    return 8 * i + j;
}

//* IntersectPrefix
//
//  Do the two prefixes overlap?
//
int
IntersectPrefix(const IPv6Addr *Prefix1, uint Prefix1Length,
                const IPv6Addr *Prefix2, uint Prefix2Length)
{
    return HasPrefix(Prefix1, Prefix2, MIN(Prefix1Length, Prefix2Length));
}

//* MapNdisBuffers
//
//  Maps the NDIS buffer chain into the kernel address space.
//  Returns FALSE upon failure.
//
int
MapNdisBuffers(NDIS_BUFFER *Buffer)
{
    uchar *Data;

    while (Buffer != NULL) {
        Data = NdisBufferVirtualAddressSafe(Buffer, LowPagePriority);
        if (Data == NULL)
            return FALSE;

        NdisGetNextBuffer(Buffer, &Buffer);
    }

    return TRUE;
}

//* GetDataFromNdis
//
//  Retrieves data from an NDIS buffer chain.
//  If the desired data is contiguous, then just returns
//  a pointer directly to the data in the buffer chain.
//  Otherwise the data is copied to the supplied buffer
//  and returns a pointer to the supplied buffer.
//
//  Returns NULL only if the desired data (offset/size)
//  does not exist in the NDIS buffer chain of
//  if DataBuffer is NULL and the data is not contiguous.
//
uchar *
GetDataFromNdis(
    NDIS_BUFFER *SrcBuffer,
    uint SrcOffset,
    uint Length,
    uchar *DataBuffer)
{
    void *DstData;
    void *SrcData;
    uint SrcSize;
    uint Bytes;

    //
    // Look through the buffer chain
    // for the beginning of the desired data.
    //
    for (;;) {
        if (SrcBuffer == NULL)
            return NULL;

        NdisQueryBuffer(SrcBuffer, &SrcData, &SrcSize);
        if (SrcOffset < SrcSize)
            break;

        SrcOffset -= SrcSize;
        NdisGetNextBuffer(SrcBuffer, &SrcBuffer);
    }

    //
    // If the desired data is contiguous,
    // then just return a pointer to it.
    //
    if (SrcOffset + Length <= SrcSize)
        return (uchar *)SrcData + SrcOffset;

    //
    // If our caller did not specify a buffer,
    // then we must fail.
    //
    if (DataBuffer == NULL)
        return NULL;

    //
    // Copy the desired data to the caller's buffer,
    // and return a pointer to the caller's buffer.
    //
    DstData = DataBuffer;
    for (;;) {
        Bytes = SrcSize - SrcOffset;
        if (Bytes > Length)
            Bytes = Length;

        RtlCopyMemory(DstData, (uchar *)SrcData + SrcOffset, Bytes);

        (uchar *)DstData += Bytes;
        Length -= Bytes;

        if (Length == 0)
            break;

        NdisGetNextBuffer(SrcBuffer, &SrcBuffer);
        if (SrcBuffer == NULL)
            return NULL;
        NdisQueryBuffer(SrcBuffer, &SrcData, &SrcSize);
        SrcOffset = 0;
    }

    return DataBuffer;
}

//* GetIPv6Header
//
//  Returns a pointer to the IPv6 header in an NDIS Packet.
//  If the header is contiguous, then just returns
//  a pointer to the data directly in the packet.
//  Otherwise the IPv6 header is copied to the supplied buffer,
//  and a pointer to the buffer is returned.
//
//  Returns NULL only if the NDIS packet is not big enough
//  to contain a header, or if HdrBuffer is NULL and the header
//  is not contiguous.
//
IPv6Header UNALIGNED *
GetIPv6Header(PNDIS_PACKET Packet, uint Offset, IPv6Header *HdrBuffer)
{
    PNDIS_BUFFER NdisBuffer;

    NdisQueryPacket(Packet, NULL, NULL, &NdisBuffer, NULL);

    return (IPv6Header UNALIGNED *)
        GetDataFromNdis(NdisBuffer, Offset, sizeof(IPv6Header),
                        (uchar *) HdrBuffer);
}


//* PacketPullupEx - extend contiguous, aligned data region.
//
//  Pulls up more data from secondary packet buffers to create a contiguous
//  buffer of at least the requested size with the requested alignment.
//
//  The alignment requirement is expressed as a power-of-2 multiple
//  plus an offset. For example, 4n+3 means the data address should
//  be a multiple of 4 plus 3.
//  NB: These two arguments are uint not UINT_PTR because we don't
//  need to express very large multiples.
//
//  For the moment, the alignment multiple should be one or two.
//  This is because Ethernet headers are 14 bytes so in practice
//  requesting 4-byte alignment would cause copying. In the future,
//  NDIS should be fixed so the network-layer header is 8-byte aligned.
//
//  So if the natural alignment (__builtin_alignof) of the needed type
//  is one or two, then supply the natural alignment and do not
//  use the UNALIGNED keyword. Otherwise, if the needed type
//  contains an IPv6 address, then supply __builtin_alignof(IPv6Addr)
//  (so you can use AddrAlign to access the addresses without copying)
//  and use UNALIGNED. Otherwise, supply 1 and use UNALIGNED.
//
//  NB: A caller can request a zero size contiguous region
//  with no alignment restriction, to move to the next buffer
//  after processing the current buffer. In this usage,
//  PacketPullupSubr will never fail.
//
uint  // Returns: new contiguous amount, or 0 if unable to satisfy request.
PacketPullupSubr(
    IPv6Packet *Packet,  // Packet to pullup.
    uint Needed,         // Minimum amount of contiguous data to return.
    uint AlignMultiple,  // Alignment multiple.
    uint AlignOffset)    // Offset from the alignment multiple.
{
    PNDIS_BUFFER Buffer;
    void *BufAddr;
    IPv6PacketAuxiliary *Aux;
    uint BufLen, Offset, CopyNow, LeftToGo;

    ASSERT(AlignMultiple <= 2);
    ASSERT((AlignMultiple & (AlignMultiple - 1)) == 0);
    ASSERT(AlignOffset < AlignMultiple);

    //
    // Check if our caller requested too much data.
    //
    if (Needed > Packet->TotalSize)
        return 0;

    //
    // Find our current position in the raw packet data.
    // REVIEW: This is exactly PositionPacketAt except
    // we want the Buffer for later use with CopyNdisToFlat.
    //
    if (Packet->NdisPacket == NULL) {
        //
        // Reset our data pointer and contiguous region counter.
        //
        Packet->Data = (uchar *)Packet->FlatData + Packet->Position;
        Packet->ContigSize = Packet->TotalSize;
    }
    else {
        //
        // Scan the NDIS buffer chain until we have reached the buffer
        // containing the current position.  Note that if we entered with
        // the position field pointing one off the end of a buffer (a common
        // case), we'll stop at the beginning of the following buffer. 
        //
        Buffer = NdisFirstBuffer(Packet->NdisPacket);
        Offset = 0;
        for (;;) {
            NdisQueryBuffer(Buffer, &BufAddr, &BufLen);
            Offset += BufLen;
            if (Packet->Position < Offset)
                break;
            NdisGetNextBuffer(Buffer, &Buffer);
        }

        //
        // Reset our data pointer and contiguous region counter to insure
        // they reflect the current position in the NDIS buffer chain.
        // 
        LeftToGo = Offset - Packet->Position;
        Packet->Data = (uchar *)BufAddr + (BufLen - LeftToGo);
        Packet->ContigSize = MIN(LeftToGo, Packet->TotalSize);
    }

    //
    // The above repositioning may result in a contiguous region
    // that will satisfy the request.
    //
    if (Needed <= Packet->ContigSize) {
        if ((PtrToUint(Packet->Data) & (AlignMultiple - 1)) == AlignOffset)
            return Packet->ContigSize;
    }

    //
    // In an attempt to prevent future pullup operations,
    // we actually pull up *more* than the requested amount.
    // But not too much more.
    //
    Needed = MAX(MIN(Packet->ContigSize, MAX_EXCESS_PULLUP), Needed);

    //
    // Allocate and initialize an auxiliary data region.
    // The data buffer follows the structure in memory,
    // with AlignOffset bytes of padding between.
    //
    Aux = ExAllocatePool(NonPagedPool, sizeof *Aux + AlignOffset + Needed);
    if (Aux == NULL)
        return 0;
    Aux->Next = Packet->AuxList;
    Aux->Data = (uchar *)(Aux + 1) + AlignOffset;
    Aux->Length = Needed;
    Aux->Position = Packet->Position;
    Packet->AuxList = Aux;

    //
    // We assume that ExAllocatePool returns aligned memory.
    //
    ASSERT((PtrToUint(Aux->Data) & (AlignMultiple - 1)) == AlignOffset);

    //
    // Copy the packet data to the auxiliary buffer.
    //
    if (Packet->NdisPacket == NULL) {
        RtlCopyMemory(Aux->Data, Packet->Data, Needed);
    }
    else {
        int Ok;

        Offset = BufLen - LeftToGo;
        Ok = CopyNdisToFlat(Aux->Data, Buffer, Offset, Needed,
                            &Buffer, &Offset);
        ASSERT(Ok);
    }

    //
    // Point our packet's data pointer at the auxiliary buffer.
    //
    Packet->Data = Aux->Data;
    return Packet->ContigSize = Needed;
}


//* PacketPullupCleanup
//
//  Cleanup auxiliary data regions that were created by PacketPullup.
//
void
PacketPullupCleanup(IPv6Packet *Packet)
{
    while (Packet->AuxList != NULL) {
        IPv6PacketAuxiliary *Aux = Packet->AuxList;
        Packet->AuxList = Aux->Next;
        ExFreePool(Aux);
    }
}


//* AdjustPacketParams
//
//  Adjust the pointers/value in the packet,
//  to move past some bytes in the packet.
//
void
AdjustPacketParams(
    IPv6Packet *Packet,
    uint BytesToSkip)
{
    ASSERT((BytesToSkip <= Packet->ContigSize) &&
           (Packet->ContigSize <= Packet->TotalSize));

    (uchar *)Packet->Data += BytesToSkip;
    Packet->ContigSize -= BytesToSkip;
    Packet->TotalSize -= BytesToSkip;
    Packet->Position += BytesToSkip;
}


//* PositionPacketAt
//
//  Adjust the pointers/values in the packet to reflect being at
//  a specific absolute position in the packet.
//
void
PositionPacketAt(
    IPv6Packet *Packet,
    uint NewPosition)
{
    if (Packet->NdisPacket == NULL) {
        //
        // This packet must be just a single contiguous region.
        // Finding the right spot is a simple matter of arithmetic.
        // We reset to the beginning and then adjust forward.
        //
        Packet->Data = Packet->FlatData;
        Packet->TotalSize += Packet->Position;
        Packet->ContigSize = Packet->TotalSize;
        Packet->Position = 0;
        AdjustPacketParams(Packet, NewPosition);
    } else {
        PNDIS_BUFFER Buffer;
        void *BufAddr;
        uint BufLen, Offset, LeftToGo;

        //
        // There may be multiple buffers comprising this packet.
        // Step through them until we arrive at the right spot.
        //
        Buffer = NdisFirstBuffer(Packet->NdisPacket);
        Offset = 0;
        for (;;) {
            NdisQueryBuffer(Buffer, &BufAddr, &BufLen);
            Offset += BufLen;
            if (NewPosition < Offset)
                break;
            NdisGetNextBuffer(Buffer, &Buffer);
        }
        LeftToGo = Offset - NewPosition;
        Packet->Data = (uchar *)BufAddr + (BufLen - LeftToGo);
        Packet->TotalSize += Packet->Position - NewPosition;
        Packet->ContigSize = MIN(LeftToGo, Packet->TotalSize);
        Packet->Position = NewPosition;
    }
}


//* GetPacketPositionFromPointer
//
//  Determines the Packet 'Position' (offset from start of packet)
//  corresponding to a given data pointer.
//
//  This isn't very efficient in some cases, so use sparingly.
//
uint
GetPacketPositionFromPointer(
    IPv6Packet *Packet,  // Packet containing pointer we're converting.
    uchar *Pointer)      // Pointer to convert to a position.
{
    PNDIS_BUFFER Buffer;
    uchar *BufAddr;
    uint BufLen, Position;
    IPv6PacketAuxiliary *Aux;

    //
    // If the IPv6Packet has no associated NDIS_PACKET, then we check
    // the flat data region. The packet may still have auxiliary buffers
    // from PacketPullup.
    //
    if (Packet->NdisPacket == NULL) {
        if (((uchar *)Packet->FlatData <= Pointer) &&
            (Pointer < ((uchar *)Packet->FlatData +
                        Packet->Position + Packet->TotalSize))) {
            //
            // Our pointer's position is just the difference between it
            // and the start of the flat data region.
            //
            return (uint)(Pointer - (uchar *)Packet->FlatData);
        }
    }

    //
    // The next place to look is the chain of auxiliary buffers
    // allocated by PacketPullup. This must succeed if there
    // is no associated NDIS_PACKET.
    //
    for (Aux = Packet->AuxList; Aux != NULL; Aux = Aux->Next) {
        if ((Aux->Data <= Pointer) && (Pointer < Aux->Data + Aux->Length))
            return Aux->Position + (uint)(Pointer - Aux->Data);
    }

    //
    // The last thing we do is search the NDIS buffer chain.
    // This must succeed.
    //
    Buffer = NdisFirstBuffer(Packet->NdisPacket);
    Position = 0;
    for (;;) {
        NdisQueryBuffer(Buffer, &BufAddr, &BufLen);
        if ((BufAddr <= Pointer) && (Pointer < BufAddr + BufLen))
            break;
        Position += BufLen;
        NdisGetNextBuffer(Buffer, &Buffer);
        ASSERT(Buffer != NULL);
    }

    return Position + (uint)(Pointer - BufAddr);
}


//* InitializePacketFromNdis
//
//  Initialize an IPv6Packet from an NDIS_PACKET.
//
void
InitializePacketFromNdis(
    IPv6Packet *Packet,
    PNDIS_PACKET NdisPacket,
    uint Offset)
{
    PNDIS_BUFFER NdisBuffer;

    RtlZeroMemory(Packet, sizeof *Packet);

    NdisGetFirstBufferFromPacket(NdisPacket, &NdisBuffer,
                                 &Packet->Data,
                                 &Packet->ContigSize,
                                 &Packet->TotalSize);

    Packet->NdisPacket = NdisPacket;
    PositionPacketAt(Packet, Offset);
}


#if DBG
//* FormatV6Address - Print an IPv6 address to a buffer.
//
//  Returns a static buffer containing the address.
//  Because the static buffer is not locked,
//  this function is only useful for debug prints.
//
//  Returns char * instead of WCHAR * because %ws
//  is not usable at DPC level in DbgPrint.
//
char *
FormatV6Address(const IPv6Addr *Addr)
{
    static char Buffer[INET6_ADDRSTRLEN];

    FormatV6AddressWorker(Buffer, Addr);
    return Buffer;
}


//* FormatV4Address - Print an IPv4 address to a buffer.
//
//  Returns a static buffer containing the address.
//  Because the static buffer is not locked,
//  this function is only useful for debug prints.
//
//  Returns char * instead of WCHAR * because %ws
//  is not usable at DPC level in DbgPrint.
//
char *
FormatV4Address(IPAddr Addr)
{
    static char Buffer[INET_ADDRSTRLEN];

    FormatV4AddressWorker(Buffer, Addr);
    return Buffer;
}
#endif // DBG


#if DBG
long DebugLogSlot = 0;
struct DebugLogEntry DebugLog[DEBUG_LOG_SIZE];

//* LogDebugEvent - Make an entry in our debug log that some event happened.
//
// The debug event log allows for "real time" logging of events
// in a circular queue kept in non-pageable memory.  Each event consists
// of an id number and a arbitrary 32 bit value.
//
void
LogDebugEvent(uint Event,  // The event that took place.
              int Arg)     // Any interesting 32 bits associated with event.
{
    uint Slot;

    //
    // Get the next slot in the log in a muliprocessor safe manner.
    //
    Slot = InterlockedIncrement(&DebugLogSlot) & (DEBUG_LOG_SIZE - 1);

    //
    // Add this event to the log along with a timestamp.
    //
    KeQueryTickCount(&DebugLog[Slot].Time);
    DebugLog[Slot].Event = Event;
    DebugLog[Slot].Arg = Arg;
}
#endif // DBG