text: //============================================================================
  // Copyright (c) 1995, Microsoft Corporation
  //
  // File: enum.c
  //
  // History:
  //      V Raman	June-25-1997  Created.
  //
  // Enumeration functions exported to IP Router Manager.
  //============================================================================
  
  
  #include "pchmgm.h"
  #pragma hdrstop
  
  
  DWORD
  GetGroupMfes(
      IN              PGROUP_ENTRY            pge,
      IN              DWORD                   dwStartSource,
      IN OUT          PBYTE                   pbBuffer,
      IN              DWORD                   dwBufferSize,
      IN OUT          PDWORD                  pdwSize,
      IN OUT          PDWORD                  pdwNumEntries,
      IN              BOOL                    bIncludeFirst,
      IN              DWORD                   dwFlags
  );
  
  
  VOID
  CopyMfe(
      IN              PGROUP_ENTRY            pge,
      IN              PSOURCE_ENTRY           pse,
      IN  OUT         PBYTE                   pb,
      IN              DWORD                   dwFlags
  );
  
  
  
  //
  // MFE enumeration
  //
  
  //----------------------------------------------------------------------------
  // GetNextMfe
  //
  //----------------------------------------------------------------------------
  
  DWORD
  GetMfe(
      IN              PMIB_IPMCAST_MFE        pmimm,
      IN  OUT         PDWORD                  pdwBufferSize,
      IN  OUT         PBYTE                   pbBuffer,
      IN              DWORD                   dwFlags
  )
  {
  
      BOOL                bGrpLock = FALSE, bGrpEntryLock = FALSE;
      
      DWORD               dwErr = NO_ERROR, dwGrpBucket, dwSrcBucket, dwSizeReqd,
                          dwInd;
  
      PGROUP_ENTRY        pge;
  
      PSOURCE_ENTRY       pse;
  
      POUT_IF_ENTRY       poie;
  
      PLIST_ENTRY         ple, pleHead;
  
  
  
      TRACEENUM3( 
          ENUM, "ENTERED GetMfe : %x, %x, Stats : %x", pmimm-> dwGroup, 
          pmimm-> dwSource, dwFlags
          );
      
      do
      {
          //
          // Find group entry
          //
  
          dwGrpBucket = GROUP_TABLE_HASH( pmimm-> dwGroup, 0 );
  
          ACQUIRE_GROUP_LOCK_SHARED( dwGrpBucket );
          bGrpLock = TRUE;
          
          pleHead = GROUP_BUCKET_HEAD( dwGrpBucket );
  
          pge = GetGroupEntry( pleHead, pmimm-> dwGroup, 0 );
  
          if ( pge == NULL )
          {
              //
              // group entry not found, quit 
              //
  
              dwErr = ERROR_NOT_FOUND;
  
              break;
          }
  
  
          //
          // acquire group entry lock and release group bucket lock
          //
          
          ACQUIRE_GROUP_ENTRY_LOCK_EXCLUSIVE( pge );
          bGrpEntryLock = TRUE;
  
  
          RELEASE_GROUP_LOCK_SHARED( dwGrpBucket );
          bGrpLock = FALSE;
  
          
          //
          // Find Source entry
          //
  
          dwSrcBucket = SOURCE_TABLE_HASH( pmimm-> dwSource, pmimm-> dwSrcMask );
  
          pleHead = SOURCE_BUCKET_HEAD( pge, dwSrcBucket );
  
          pse = GetSourceEntry( pleHead, pmimm-> dwSource, pmimm-> dwSrcMask );
  
          if ( pse == NULL )
          {
              //
              // Source entry not found, quit
              //
  
              dwErr = ERROR_NOT_FOUND;
  
              break;
          }
          
          
          //
          // check buffersize requirements 
          //
  
          dwSizeReqd = ( dwFlags ) ?
                          ( (dwFlags == MGM_MFE_STATS_0) ?
                             SIZEOF_MIB_MFE_STATS( pse-> dwMfeIfCount ) :
                             SIZEOF_MIB_MFE_STATS_EX( 
                              pse-> dwMfeIfCount ) ) :
                          SIZEOF_MIB_MFE( pse-> dwMfeIfCount );
  
          if ( *pdwBufferSize < dwSizeReqd )
          {
              //
              // buffer supplied is too small to fit the MFE
              //
              
              *pdwBufferSize = dwSizeReqd;
  
              dwErr = ERROR_INSUFFICIENT_BUFFER;
  
              break;
          }
  
          
          //
          // if mfe statistics have been requested and
          //    mfe is in the kernel 
          //      get it 
          //
  
          if ( dwFlags && pse-> bInForwarder )
          {
              GetMfeFromForwarder( pge, pse );
          }
  
  #if 1
          CopyMfe( pge, pse, pbBuffer, dwFlags );
  #else        
          //
          // copy base MFE into user supplied buffer
          //
  
          pmimms = ( PMIB_IPMCAST_MFE_STATS ) pbBuffer;
  
          pmimms-> dwGroup            = pge-> dwGroupAddr;
          pmimms-> dwSource           = pse-> dwSourceAddr;
          pmimms-> dwSrcMask          = pse-> dwSourceMask;
  
          pmimms-> dwInIfIndex        = pse-> dwInIfIndex;
          pmimms-> dwUpStrmNgbr       = pse-> dwUpstreamNeighbor;
          pmimms-> dwInIfProtocol     = pse-> dwInProtocolId;
  
          pmimms-> dwRouteProtocol    = pse-> dwRouteProtocol;
          pmimms-> dwRouteNetwork     = pse-> dwRouteNetwork;
          pmimms-> dwRouteMask        = pse-> dwRouteMask;
          
          pmimms-> ulNumOutIf         = pse-> imsStatistics.ulNumOutIf;
          pmimms-> ulInPkts           = pse-> imsStatistics.ulInPkts;
          pmimms-> ulInOctets         = pse-> imsStatistics.ulInOctets;
          pmimms-> ulPktsDifferentIf  = pse-> imsStatistics.ulPktsDifferentIf;
          pmimms-> ulQueueOverflow    = pse-> imsStatistics.ulQueueOverflow;
  
          
          MgmElapsedSecs( &pse-> liCreationTime, &pmimms-> ulUpTime );
                                          
          pmimms-> ulExpiryTime = pse-> dwTimeOut - pmimms-> ulUpTime;
  
  
          //
          // copy all the OIL entries
          //
  
          pleHead = &pse-> leMfeIfList;
          
          for ( ple = pleHead-> Flink, dwInd = 0; 
                ple != pleHead; 
                ple = ple-> Flink, dwInd++ )
          {
              poie = CONTAINING_RECORD( ple, OUT_IF_ENTRY, leIfList );
  
              pmimms-> rgmiosOutStats[ dwInd ].dwOutIfIndex = 
                  poie-> imosIfStats.dwOutIfIndex;
                  
              pmimms-> rgmiosOutStats[ dwInd ].dwNextHopAddr = 
                  poie-> imosIfStats.dwNextHopAddr;
                  
              pmimms-> rgmiosOutStats[ dwInd ].ulTtlTooLow = 
                  poie-> imosIfStats.ulTtlTooLow;
                  
              pmimms-> rgmiosOutStats[ dwInd ].ulFragNeeded = 
                  poie-> imosIfStats.ulFragNeeded;
                  
              pmimms-> rgmiosOutStats[ dwInd ].ulOutPackets = 
                  poie-> imosIfStats.ulOutPackets;
                  
              pmimms-> rgmiosOutStats[ dwInd ].ulOutDiscards = 
                  poie-> imosIfStats.ulOutDiscards;
          }
  #endif
  
      } while ( FALSE );
  
  
      //
      // release locks are appropriate
      //
      
      if ( bGrpEntryLock )
      {
          RELEASE_GROUP_ENTRY_LOCK_EXCLUSIVE( pge );
      }
  
      if ( bGrpLock )
      {
          RELEASE_GROUP_LOCK_SHARED( dwGrpBucket );
      }
  
  
      TRACEENUM1( ENUM, "LEAVING GetMfe :: %x", dwErr );
      
      return dwErr;
  }
  
  
  
  //----------------------------------------------------------------------------
  // GetNextMfe
  //
  //----------------------------------------------------------------------------
  
  DWORD
  GetNextMfe(
      IN              PMIB_IPMCAST_MFE        pmimmStart,
      IN  OUT         PDWORD                  pdwBufferSize,
      IN  OUT         PBYTE                   pbBuffer,
      IN  OUT         PDWORD                  pdwNumEntries,
      IN              BOOL                    bIncludeFirst,
      IN              DWORD                   dwFlags
  )
  {
  
      BOOL            bFound, bgeLock = FALSE;
      
      DWORD           dwGrpBucket, dwErr = NO_ERROR, dwBufferLeft, 
                      dwStartSource, dwSize;
  
      PBYTE           pbStart;
      
      PGROUP_ENTRY    pge;
      
      PLIST_ENTRY     ple, pleMasterHead, pleGrpBucket;
  
      
  
      TRACEENUM2( 
          ENUM, "ENTERED GetNextMfe (G, S) = (%x, %x)", pmimmStart-> dwGroup, 
          pmimmStart-> dwSource 
          );
  
  
      do
      {
          //
          // 1. Lock group hash bucket.
          //
  
          dwGrpBucket = GROUP_TABLE_HASH( pmimmStart-> dwGroup, 0 );
  
          ACQUIRE_GROUP_LOCK_SHARED( dwGrpBucket );
  
                  
          //
          // 2. merge temp and master lists
          //      - Lock temp list
          //      - merge temp with master list
          //      - unlock temp list
          //
  
          ACQUIRE_TEMP_GROUP_LOCK_EXCLUSIVE();
  
          MergeTempAndMasterGroupLists( TEMP_GROUP_LIST_HEAD() );
  
          ACQUIRE_MASTER_GROUP_LOCK_SHARED();
  
          RELEASE_TEMP_GROUP_LOCK_EXCLUSIVE();
  
  
          pleMasterHead = MASTER_GROUP_LIST_HEAD();
  
          ple = pleMasterHead-> Flink;
  
          //
          // To retrieve the next set of group entries in lexicographic order, 
          // given a group entry (in this case specified by pmimmStart-> dwGroup)
          // the master group list must be walked from the head until either 
          // the group entry specified is found or the next "higher" group entry 
          // is found.  This is expensive.
          //
          // As an optimization the group specified (pmimmStart-> dwGroup) is
          // looked up in the group hash table.  If an entry is found, then the
          // group entry contains links into the master (lexicographic) group
          // list. These links can the used to determine the next entries in 
          // the group list.  This way we can quickly find an group entry in
          // the master list rather than walk the master group list from the
          // beginning.
          //
          // It should be noted that in case the group entry specified in not
          // present in the group hash table, it will be necessary to walk the
          // master group list from the start.
          //
          // Each group entry is present in two lists, the hash bucket list 
          // and either temp group list or the master group list.
          //
          // For this optimization to "work", it must be ensured that an entry
          // present in the hash table is also present in the master
          // group list.  To ensure this the temp group list is merged into
          // the master group list before searching the group hash table for
          // the specified entry.
          //
  
          
          //
          // At this point the group under consideration (pmimmStart-> dwGroup),
          // cannot be added to either the hash bucket or master group list
          // if it is not already present because both the group hash bucket lock
          // and the master list lock have been acquired.
          //
  
          //
          // 3. find group entry in the hash list
          //
  
          pleGrpBucket = GROUP_BUCKET_HEAD( dwGrpBucket );
          
          pge = GetGroupEntry( pleGrpBucket, pmimmStart-> dwGroup, 0 );
  
          if ( pge != NULL )
          {
              //
              // group entry for pmimmStart-> dwGroup is present. lock the entry.
              //
  
              ACQUIRE_GROUP_ENTRY_LOCK_EXCLUSIVE( pge );
              bgeLock = TRUE;
  
              //
              // release group hash bucket lock
              //
  
              RELEASE_GROUP_LOCK_SHARED( dwGrpBucket );
          }
          
          else
          {
              
              //
              // group entry is not present in the hash table, which implies
              // that the group entry is not present at all.
              //
  
              //
              // release group hash bucket lock
              //
  
              RELEASE_GROUP_LOCK_SHARED( dwGrpBucket );
              
              //
              // 3.1 Walk master list from the start to determine the next 
              //     highest group entry.
              //
  
              bFound = FindGroupEntry( 
                          pleMasterHead, pmimmStart-> dwGroup, 0,
                          &pge, FALSE
                          );
  
              if ( !bFound && pge == NULL )
              {
                  //
                  // No more group entries left to enumerate
                  //
  
                  dwErr = ERROR_NO_MORE_ITEMS;
  
                  RELEASE_MASTER_GROUP_LOCK_SHARED();
  
                  break;
              }
  
  
              //
              // Next group entry found.  lock it
              //
  
              ACQUIRE_GROUP_ENTRY_LOCK_EXCLUSIVE( pge );
              bgeLock = TRUE;
  
              bIncludeFirst = TRUE;
          }
  
  
          //
          // At this point we have the group entry we want which is
          // either the one for pmimmStart-> dwGroup OR the next higher
          // one (if there is no group entry for pmimmStart-> Group).
          //
  
          //
          // 4. Now get as many source entries as will fit into 
          //    the buffer provided.
          //
  
          dwBufferLeft    = *pdwBufferSize;
  
          pbStart         = pbBuffer;
  
          *pdwNumEntries  = 0;
  
          dwStartSource   = ( bIncludeFirst ) ? 0 : pmimmStart-> dwSource;
  
          dwSize          = 0;
  
          
          while ( ( dwErr = GetGroupMfes( pge, dwStartSource, pbStart, 
                                dwBufferLeft, &dwSize, pdwNumEntries, 
                                bIncludeFirst, dwFlags ) ) 
                  == ERROR_MORE_DATA )
          {
              //
              // more data items will fit into this buffer, but no more 
              // source entries available in this group entry
              //
              // 4.1 Move forward to next group entry.
              //
  
              pbStart         += dwSize;
  
              dwBufferLeft    -= dwSize;
  
              dwSize          = 0;
  
              dwStartSource   = 0;
  
              
              //
              // 4.1.1 Release this group entry lock
              //
              
              RELEASE_GROUP_ENTRY_LOCK_EXCLUSIVE( pge );
  
  
              //
              // 4.1.2 get next entry lock
              //
  
              ple = pge-> leGrpList.Flink;
  
              if ( ple == pleMasterHead )
              {
                  //
                  // No more group entries in the master group list.
                  // All MFEs have been exhausted. So quit.
                  //
                  
                  dwErr = ERROR_NO_MORE_ITEMS;
  
                  bgeLock = FALSE;
                  
                  break;
              }
  
              pge = CONTAINING_RECORD( ple, GROUP_ENTRY, leGrpList );
  
              ACQUIRE_GROUP_ENTRY_LOCK_EXCLUSIVE( pge );
  
              dwStartSource = 0;
  
              bIncludeFirst = TRUE;
          }
  
  
          //
          // 5. you have packed as much as possible into the buffer
          //
          //  Clean up and return the correct error code.
          //
          
          if ( bgeLock )
          {
              RELEASE_GROUP_ENTRY_LOCK_EXCLUSIVE( pge );
          }
  
  
          if ( dwErr == ERROR_INSUFFICIENT_BUFFER )
          {
              //
              // ran out of buffer.  If there is at least one Mfe
              // packed into the buffer provided then it is ok.
              //
  
              if ( *pdwNumEntries != 0 )
              {
                  dwErr = ERROR_MORE_DATA;
              }
  
              else
              {
                  //
                  // not even one entry could be packed into the buffer
                  // return the size required for this so that an
                  // appropriately sized buffer can be allocated for the
                  // next call.
                  //
  
                  *pdwBufferSize = dwSize;
              }
          }
  
          RELEASE_MASTER_GROUP_LOCK_SHARED();
          
      } while ( FALSE );
  
  
      TRACEENUM1( ENUM, "LEAVING GetNextMfe : %x", dwErr );
  
      return dwErr;
  }
  
  
  
  //----------------------------------------------------------------------------
  //
  // GetGroupMfes
  //
  // Retrieves as many MFEs for a group starting at the specified source.
  // Assumes that the group entry is locked.
  //----------------------------------------------------------------------------
  
  DWORD
  GetGroupMfes(
      IN              PGROUP_ENTRY            pge,
      IN              DWORD                   dwStartSource,
      IN OUT          PBYTE                   pbBuffer,
      IN              DWORD                   dwBufferSize,
      IN OUT          PDWORD                  pdwSize,
      IN OUT          PDWORD                  pdwNumEntries,
      IN              BOOL                    bIncludeFirst,
      IN              DWORD                   dwFlags
  )
  {
  
      BOOL            bFound;
      
      DWORD           dwErr = ERROR_MORE_DATA, dwSrcBucket,
                      dwSizeReqd, dwInd;
  
      PSOURCE_ENTRY   pse = NULL;
  
      PLIST_ENTRY     pleMasterHead, pleSrcBucket, ple = NULL,
                      pleSrc;
      
      POUT_IF_ENTRY   poie = NULL;
  
  
      TRACEENUM2( 
          ENUM, "ENTERED GetGroupMfes : %x, %x", 
          pge-> dwGroupAddr, dwStartSource
          );
  
      do
      {
          //
          // merge temp and group source lists
          //
  
          MergeTempAndMasterSourceLists( pge );
  
          
          //
          // similar to the group lookup, optimize the source lookup
          // by first trying to find the source entry in the source
          // hash table.  
          //
          // If found in the hash table then use the entry's links 
          // the into master source list to find next entry.
          //
          // if not found in the hash table walk the master list from
          // the beginning to determine the next entry.
          //
  
          pleMasterHead   = MASTER_SOURCE_LIST_HEAD( pge );
  
          dwSrcBucket     = SOURCE_TABLE_HASH( dwStartSource, 0 );
  
          pleSrcBucket    = SOURCE_BUCKET_HEAD( pge, dwSrcBucket );
  
  
          bFound = FindSourceEntry( pleSrcBucket, dwStartSource, 0, &pse, TRUE );
  
          if ( !bFound )
          {
              //
              // source entry is not present in the hash table
              // Walk the master source list from the start.
              //
  
              pse = NULL;
              
              FindSourceEntry( pleMasterHead, 0, 0, &pse, FALSE );
  
  
              //
              // No next entry found in the master list.  Implies
              // no more sources in the master source list for this group.
              //
              
              if ( pse == NULL )
              {
                  break;
              }
          }
  
          else
          {
              //
              // Entry for starting source found in hash table.
              // Use its links into the master list to get next entry.
              //
  
              if ( !bIncludeFirst )
              {
                  ple = pse-> leSrcList.Flink;
  
                  pse = CONTAINING_RECORD( ple, SOURCE_ENTRY, leSrcList );
              }
          }
  
  
          //
          // At this point the entry pointed to by pse is the first entry
          // the needs to be packed into the buffer supplied.  Starting
          // with this source entry keep packing MFEs into the
          // buffer till there are no more MFEs for this group.
          //
          
          pleSrc = &pse-> leSrcList;
  
          //
          // while there are source entries for this group entry
          //
          
          while ( pleSrc != pleMasterHead )
          {
              pse = CONTAINING_RECORD( pleSrc, SOURCE_ENTRY, leSrcList );
  
  
              //
              // Is this source entry an MFE
              //
              
              if ( !IS_VALID_INTERFACE( pse-> dwInIfIndex, 
                      pse-> dwInIfNextHopAddr ) )
              {
                  pleSrc = pleSrc-> Flink;
  
                  continue;
              }
  
  
              //
              // This source entry is an MFE also.
              //
  
              //
              // Check if enough space left in the buffer to fit this MFE.
              //
              // If not and not a single MFE is present in the buffer then
              // return the size required to fit this MFE.
              //
              
              dwSizeReqd = ( dwFlags ) ? 
                              ( ( dwFlags == MGM_MFE_STATS_0 ) ?
                                  SIZEOF_MIB_MFE_STATS( pse-> dwMfeIfCount ) :
                                  SIZEOF_MIB_MFE_STATS_EX( 
                                      pse-> dwMfeIfCount 
                                  ) ) :
                              SIZEOF_MIB_MFE( pse-> dwMfeIfCount );
  
              if ( dwBufferSize < dwSizeReqd )
              {
                  dwErr = ERROR_INSUFFICIENT_BUFFER;
  
                  if ( *pdwNumEntries == 0 )
                  {
                      *pdwSize = dwSizeReqd;
                  }
  
                  break;
              }
  
  
              //
              // If MFE stats have been requested and
              //    MFE is present in the forwarder
              //      get them.
              //
  
              if ( dwFlags && pse-> bInForwarder )
              {
                  //
                  // MFE is currently in the forwarder.  Query it and update
                  // stats user mode.
                  //
  
                  GetMfeFromForwarder( pge, pse );
              }
  
              
              //
              // copy base MFE into user supplied buffer
              //
  
  
              CopyMfe( pge, pse, pbBuffer, dwFlags );
  
              pbBuffer        += dwSizeReqd;
  
              dwBufferSize    -= dwSizeReqd;
  
              *pdwSize        += dwSizeReqd;
  
              (*pdwNumEntries)++;
  
              pleSrc           = pleSrc-> Flink;
          }
          
      } while ( FALSE );
  
  
      TRACEENUM2( ENUM, "LEAVING GetGroupsMfes : %d %d", *pdwNumEntries, dwErr );
  
      return dwErr;
  }
  
  
  //============================================================================
  // Group Enumeration
  //
  //============================================================================
  
  
  PGROUP_ENUMERATOR
  VerifyEnumeratorHandle(
      IN              HANDLE                  hEnum
  )
  {
  
      DWORD                       dwErr;
      
      PGROUP_ENUMERATOR           pgeEnum;
  
  
      
      pgeEnum = (PGROUP_ENUMERATOR) 
                          ( ( (ULONG_PTR) hEnum ) 
                                  ^ (ULONG_PTR) MGM_ENUM_HANDLE_TAG );
  
      try
      {
          if ( pgeEnum-> dwSignature != MGM_ENUM_SIGNATURE )
          {
              dwErr = ERROR_INVALID_PARAMETER;
  
              TRACE0( ANY, "Invalid Enumeration handle" );
  
              pgeEnum = NULL;
          }
      }
      
      except ( GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION ? 
                                     EXCEPTION_EXECUTE_HANDLER :
                                     EXCEPTION_CONTINUE_SEARCH )
      {
          dwErr = ERROR_INVALID_PARAMETER;
  
          TRACE0( ANY, "Invalid enumeration handle" );
          
          pgeEnum = NULL;
      }
  
      return pgeEnum;
  }
  
  
  //
  // Get Memberships for buckets
  //
  
  DWORD
  GetNextGroupMemberships(
      IN              PGROUP_ENUMERATOR       pgeEnum,
      IN OUT          PDWORD                  pdwBufferSize,
      IN OUT          PBYTE                   pbBuffer,
      IN OUT          PDWORD                  pdwNumEntries
  )
  {
  
      BOOL            bIncludeFirst = TRUE, bFound;
  
      DWORD           dwMaxEntries, dwGrpBucket, dwErr = ERROR_NO_MORE_ITEMS;
  
      PGROUP_ENTRY    pge = NULL;
  
      PSOURCE_GROUP_ENTRY psge;
      
      PLIST_ENTRY     ple, pleGrpHead;
  
  
      
      do
      {
          //
          // Compute the number of entries that will fit into the buffer
          //
  
          dwMaxEntries = (*pdwBufferSize) / sizeof( SOURCE_GROUP_ENTRY );
  
  
          //
          // STEP I :
          //
  
          //
          // position the start of the GetNext to the group entry that was
          // the last enumerated by the previous GetNext operation
          //
  
          //
          // Find the last group entry retrieved by the previous get operation. 
          //
  
          dwGrpBucket = GROUP_TABLE_HASH( 
                          pgeEnum-> dwLastGroup, pgeEnum-> dwLastGroupMask 
                          );
  
          ACQUIRE_GROUP_LOCK_SHARED( dwGrpBucket );
  
          pleGrpHead = GROUP_BUCKET_HEAD( dwGrpBucket );
          
          bFound = FindGroupEntry( 
                      pleGrpHead, pgeEnum-> dwLastGroup, 
                      pgeEnum-> dwLastGroupMask, &pge, TRUE
                      );
  
          if ( bFound )
          {
              //
              // group entry found
              //
              
              bIncludeFirst = !pgeEnum-> bEnumBegun;
          }
  
          
  
          //
          // last group entry retrieved by previous getnext is no 
          // longer present
          //
  
          //
          // check if there are any more group entries present in 
          // the same bucket
          //
  
          else if ( pge != NULL )
          {
              //
              // Next group entry in the same group bucket.  
              // For a new group start from the first source bucket, 
              // first source entry. 
              //
  
              pgeEnum-> dwLastSource      = 0;
              pgeEnum-> dwLastSourceMask  = 0;
          }
  
          
          else // ( pge == NULL )
          {
              //
              // no more entries in this group bucket, move to next
              // non-empty group bucket entry.
              //
  
              //
              // skip empty buckets in the group hash table
              //
              
              do
              {
                  RELEASE_GROUP_LOCK_SHARED( dwGrpBucket );
  
                  dwGrpBucket++;
  
                  if ( dwGrpBucket >= GROUP_TABLE_SIZE ) 
                  {
                      //
                      // Entire hash table has been traversed, quit
                      //
  
                      break;
                  }
  
                  //
                  // Move to next group bucket
                  //
                  
                  ACQUIRE_GROUP_LOCK_SHARED( dwGrpBucket );
  
                  pleGrpHead = GROUP_BUCKET_HEAD( dwGrpBucket );
  
  
                  //
                  // Check if any group entries present
                  //
                  
                  if ( !IsListEmpty( pleGrpHead ) )
                  {
                      //
                      // group bucket has at least on group entry
                      //
                      
                      pge = CONTAINING_RECORD( 
                              pleGrpHead-> Flink, GROUP_ENTRY, leGrpHashList
                              );
  
                      //
                      // For a new group start from the first source bucket, 
                      // first source entry. 
                      //
  
                      pgeEnum-> dwLastSource      = 0;
                      pgeEnum-> dwLastSourceMask  = 0;
  
                      break;
                  }
  
                  //
                  // Empty group bucket, move to next one
                  //
                  
              } while ( TRUE );
          }
          
  
          //
          // if all hash buckets have been traversed, quit.
          //
          
          if ( dwGrpBucket >= GROUP_TABLE_SIZE )
          {
              break;
          }
          
  
          //
          // STEP II:
          //
  
          //
          // start retrieving group membership entries
          //
  
  #pragma prefast(suppress: 11, pge will be initialized else we would broken out above (PREFast bug 758))
          ple = &pge-> leGrpHashList;
          
  
          //
          // Walk each hash bucket starting from dwGrpBucket to GROUP_TABLE_SIZE
          //
  
          while ( dwGrpBucket < GROUP_TABLE_SIZE )
          {
              //
              // For each group hash table bucket
              //
              
              while ( ple != pleGrpHead )
              {
                  //
                  // For each group entry in the bucket
                  //
  
                  pge = CONTAINING_RECORD( ple, GROUP_ENTRY, leGrpHashList );
  
                  ACQUIRE_GROUP_ENTRY_LOCK_SHARED( pge );
                  
                  dwErr = GetNextMembershipsForThisGroup( 
                              pge, pgeEnum, bIncludeFirst, pbBuffer,
                              pdwNumEntries, dwMaxEntries
                              );
  
                  RELEASE_GROUP_ENTRY_LOCK_SHARED( pge );
                  
                  if ( dwErr == ERROR_MORE_DATA )
                  {
                      //
                      // User supplied buffer is full.
                      //
  
                      break;
                  }
                  
  
                  //
                  // Move to next entry
                  //
                  
                  ple = ple-> Flink;
  
                  //
                  // Next group entry in the same group bucket.  
                  // For a new group start from the first source bucket, 
                  // first source entry. 
                  //
                  
                  pgeEnum-> dwLastSource = 0;
                  
                  pgeEnum-> dwLastSourceMask = 0;
  
                  bIncludeFirst = TRUE;
              }
  
              RELEASE_GROUP_LOCK_SHARED( dwGrpBucket );
  
              if ( dwErr == ERROR_MORE_DATA )
              {
                  break;
              }
  
  
              //
              // Move to next group bucket
              //
              
              dwGrpBucket++;
  
              //
              // skip empty group hash buckets
              //
              
              while ( dwGrpBucket < GROUP_TABLE_SIZE )
              {
                  ACQUIRE_GROUP_LOCK_SHARED( dwGrpBucket );
  
                  pleGrpHead = GROUP_BUCKET_HEAD( dwGrpBucket );
  
                  if ( !IsListEmpty( pleGrpHead ) )
                  {
                      break;
                  }
  
                  RELEASE_GROUP_LOCK_SHARED( dwGrpBucket );
  
                  dwGrpBucket++;
              } 
  
              
              if ( dwGrpBucket >= GROUP_TABLE_SIZE )
              {
                  //
                  // All group buckets have traversed.  End of enumeration
                  //
                  
                  dwErr = ERROR_NO_MORE_ITEMS;
              }
  
              else
              {
                  //
                  // New group hash bucket, start from source entry 0.
                  //
                  
                  ple = pleGrpHead-> Flink;
                  
                  pgeEnum-> dwLastSource      = 0;
                  pgeEnum-> dwLastSourceMask  = 0;
                  bIncludeFirst               = TRUE;
              }            
          }
          
      } while ( FALSE );
  
      pgeEnum-> bEnumBegun    = TRUE;
  
  
      //
      // Store the position where the enumeration ended
      //
      
      psge = (PSOURCE_GROUP_ENTRY) pbBuffer;
  
      if ( *pdwNumEntries )
      {
          pgeEnum-> dwLastSource      = psge[ *pdwNumEntries - 1 ].dwSourceAddr;
          
          pgeEnum-> dwLastSourceMask  = psge[ *pdwNumEntries - 1 ].dwSourceMask;
          
          pgeEnum-> dwLastGroup       = psge[ *pdwNumEntries - 1 ].dwGroupAddr;
          
          pgeEnum-> dwLastGroupMask   = psge[ *pdwNumEntries - 1 ].dwGroupMask;
      }
  
      else
      {
          pgeEnum-> dwLastSource      = 0xFFFFFFFF;
          
          pgeEnum-> dwLastSourceMask  = 0xFFFFFFFF;
          
          pgeEnum-> dwLastGroup       = 0xFFFFFFFF;
          
          pgeEnum-> dwLastGroupMask   = 0xFFFFFFFF;
      }
      
      return dwErr;
  }
  
  
  //----------------------------------------------------------------------------
  // GetMemberships for Group
  //
  //----------------------------------------------------------------------------
  
  DWORD
  GetNextMembershipsForThisGroup(
      IN              PGROUP_ENTRY            pge,
      IN OUT          PGROUP_ENUMERATOR       pgeEnum,
      IN              BOOL                    bIncludeFirst,
      IN OUT          PBYTE                   pbBuffer,
      IN OUT          PDWORD                  pdwNumEntries,
      IN              DWORD                   dwMaxEntries
  )
  {
  
      BOOL                    bFound;
      
      DWORD                   dwErr = ERROR_NO_MORE_ITEMS, dwSrcBucket;
  
      PSOURCE_GROUP_ENTRY     psgBuffer;
      
      PSOURCE_ENTRY           pse = NULL;
      
      PLIST_ENTRY             pleSrcHead, ple;
  
      
      
      do
      {
  
          if ( *pdwNumEntries >= dwMaxEntries )
          {
              //
              // quit here.
              //
  
              dwErr = ERROR_MORE_DATA;
  
              break;
          }
  
          
          psgBuffer = (PSOURCE_GROUP_ENTRY) pbBuffer;
          
  
          //
          // STEP I:
          // Position start of enumeration  
          //
  
          dwSrcBucket = SOURCE_TABLE_HASH( 
                          pgeEnum-> dwLastSource, pgeEnum-> dwLastSourceMask 
                          );
                          
          pleSrcHead = SOURCE_BUCKET_HEAD( pge, dwSrcBucket );
  
          bFound = FindSourceEntry(
                      pleSrcHead, pgeEnum-> dwLastSource, 
                      pgeEnum-> dwLastSourceMask, &pse, TRUE
                      );
  
          if ( bFound )
          {
              if ( ( bIncludeFirst ) && !IsListEmpty( &pse-> leOutIfList ) )
              {
                  //
                  // the first group membership found.
                  //
  
                  psgBuffer[ *pdwNumEntries ].dwSourceAddr = pse-> dwSourceAddr;
                  
                  psgBuffer[ *pdwNumEntries ].dwSourceMask = pse-> dwSourceMask;
  
                  psgBuffer[ *pdwNumEntries ].dwGroupAddr   = pge-> dwGroupAddr;
  
                  psgBuffer[ (*pdwNumEntries)++ ].dwGroupMask  = pge-> dwGroupMask;
                  
                  if ( *pdwNumEntries >= dwMaxEntries )
                  {
                      //
                      // buffer full. quit here.
                      //
  
                      dwErr = ERROR_MORE_DATA;
  
                      break;
                  }
  
                  //
                  // move to next source
                  //
  
                  ple = pse-> leSrcHashList.Flink;
              }
  
              else
              {
                  ple = pse-> leSrcHashList.Flink;
              }
          }
  
          else if ( pse != NULL )
          {
              ple = &pse-> leSrcHashList;
          }
  
          else
          {
              ple = pleSrcHead-> Flink;
          }
  
          
          //
          // STEP II:
          //
          // enumerate group memberships
          //
  
          while ( *pdwNumEntries < dwMaxEntries ) 
          {
              //
              // for each source bucket
              //
              
              while ( ( ple != pleSrcHead ) && 
                      ( *pdwNumEntries < dwMaxEntries ) )
              {
                  //
                  // for each source entry in the bucket
                  //
  
                  //
                  // if group membership exists for this source
                  //
  
                  pse = CONTAINING_RECORD( ple, SOURCE_ENTRY, leSrcHashList );
                  
                  if ( !IsListEmpty( &pse-> leOutIfList ) )
                  {
                      psgBuffer[ *pdwNumEntries ].dwSourceAddr = 
                          pse-> dwSourceAddr;
                      
                      psgBuffer[ *pdwNumEntries ].dwSourceMask = 
                          pse-> dwSourceMask;
  
                      psgBuffer[ *pdwNumEntries ].dwGroupAddr   = 
                          pge-> dwGroupAddr;
  
                      psgBuffer[ (*pdwNumEntries)++ ].dwGroupMask  = 
                          pge-> dwGroupMask;
                      
                      if ( *pdwNumEntries >= dwMaxEntries )
                      {
                          dwErr = ERROR_MORE_DATA;
                      }
                  }
  
                  ple = ple-> Flink;
              }
  
              dwSrcBucket++;
  
              if ( dwSrcBucket < SOURCE_TABLE_SIZE )
              {
                  pleSrcHead = SOURCE_BUCKET_HEAD( pge, dwSrcBucket );
  
                  ple = pleSrcHead-> Flink;
              }
  
              else
              {
                  //
                  // all source buckets for this group have been 
                  // traversed.  quit this group entry
                  //
                  
                  break;
              }
          }
          
      } while ( FALSE );
  
      return dwErr;
  }
  
  
  //----------------------------------------------------------------------------
  // Copy the MFE (optionally with stats) 
  //
  //----------------------------------------------------------------------------
  
  VOID
  CopyMfe(
      IN              PGROUP_ENTRY            pge,
      IN              PSOURCE_ENTRY           pse,
      IN  OUT         PBYTE                   pb,
      IN              DWORD                   dwFlags
  )
  {
      DWORD                   dwInd;
      
      PLIST_ENTRY             ple, pleHead;
      
      POUT_IF_ENTRY           poie;
      
      PMIB_IPMCAST_MFE        pmimm = NULL;
  
      PMIB_IPMCAST_MFE_STATS  pmimms = NULL;
  
      PMIB_IPMCAST_OIF_STATS  pmimos = NULL;
     
      //
      // copy base MFE into user supplied buffer
      //
  
      if ( dwFlags )
      {
          //
          // Need to base MFE
          //
  
          pmimms = ( PMIB_IPMCAST_MFE_STATS ) pb;
  
          pmimms-> dwGroup            = pge-> dwGroupAddr;
          pmimms-> dwSource           = pse-> dwSourceAddr;
          pmimms-> dwSrcMask          = pse-> dwSourceMask;
  
          pmimms-> dwInIfIndex        = pse-> dwInIfIndex;
          pmimms-> dwUpStrmNgbr       = pse-> dwUpstreamNeighbor;
          pmimms-> dwInIfProtocol     = pse-> dwInProtocolId;
          
          pmimms-> dwRouteProtocol    = pse-> dwRouteProtocol;
          pmimms-> dwRouteNetwork     = pse-> dwRouteNetwork;
          pmimms-> dwRouteMask        = pse-> dwRouteMask;
          
          MgmElapsedSecs( &pse-> liCreationTime, &pmimms-> ulUpTime );
  
          pmimms-> ulExpiryTime = pse-> dwTimeOut - pmimms-> ulUpTime;
          
  
          //
          // Copy incoming stats
          //
          
          pmimms-> ulNumOutIf         = pse-> dwMfeIfCount;
          pmimms-> ulInPkts           = pse-> imsStatistics.ulInPkts;
          pmimms-> ulInOctets         = pse-> imsStatistics.ulInOctets;
          pmimms-> ulPktsDifferentIf  = pse-> imsStatistics.ulPktsDifferentIf;
          pmimms-> ulQueueOverflow    = pse-> imsStatistics.ulQueueOverflow;
  
          if ( dwFlags & MGM_MFE_STATS_1 )
          {
              PMIB_IPMCAST_MFE_STATS_EX  pmimmsex = 
                  ( PMIB_IPMCAST_MFE_STATS_EX ) pb;
  
              pmimmsex-> ulUninitMfe      = pse-> imsStatistics.ulUninitMfe;
              pmimmsex-> ulNegativeMfe    = pse-> imsStatistics.ulNegativeMfe;
              pmimmsex-> ulInDiscards     = pse-> imsStatistics.ulInDiscards;
              pmimmsex-> ulInHdrErrors    = pse-> imsStatistics.ulInHdrErrors;
              pmimmsex-> ulTotalOutPackets= pse-> imsStatistics.ulTotalOutPackets;
  
              pmimos = pmimmsex-> rgmiosOutStats;
          }
  
          else
          {
              pmimos = pmimms-> rgmiosOutStats;
          }
  
          //
          // copy all the OIL entries
          //
  
          pleHead = &pse-> leMfeIfList;
          
          for ( ple = pleHead-> Flink, dwInd = 0; 
                ple != pleHead; 
                ple = ple-> Flink, dwInd++ )
          {
              poie = CONTAINING_RECORD( ple, OUT_IF_ENTRY, leIfList );
  
              pmimos[ dwInd ].dwOutIfIndex = poie-> dwIfIndex;
              pmimos[ dwInd ].dwNextHopAddr = poie-> dwIfNextHopAddr;
  
              //
              // Copy outgoing stats
              //
              
              pmimos[ dwInd ].ulTtlTooLow = poie-> imosIfStats.ulTtlTooLow;
              pmimos[ dwInd ].ulFragNeeded = poie-> imosIfStats.ulFragNeeded;
              pmimos[ dwInd ].ulOutPackets = poie-> imosIfStats.ulOutPackets;
              pmimos[ dwInd ].ulOutDiscards = poie-> imosIfStats.ulOutDiscards;
          }
      }
  
      else
      {
          //
          // Need to copy non-stats MFE structure only
          //
  
          pmimm = (PMIB_IPMCAST_MFE) pb;
  
          pmimm-> dwGroup             = pge-> dwGroupAddr;
          pmimm-> dwSource            = pse-> dwSourceAddr;
          pmimm-> dwSrcMask           = pse-> dwSourceMask;
  
          pmimm-> dwInIfIndex         = pse-> dwInIfIndex;
          pmimm-> dwUpStrmNgbr        = pse-> dwUpstreamNeighbor;
          pmimm-> dwInIfProtocol      = pse-> dwInProtocolId;
  
          pmimm-> dwRouteProtocol     = pse-> dwRouteProtocol;
          pmimm-> dwRouteNetwork      = pse-> dwRouteNetwork;
          pmimm-> dwRouteMask         = pse-> dwRouteMask;
          
          pmimm-> ulNumOutIf          = pse-> dwMfeIfCount;
  
          MgmElapsedSecs( &pse-> liCreationTime, &pmimm-> ulUpTime );
  
          pmimm-> ulExpiryTime = pse-> dwTimeOut - pmimm-> ulUpTime;
  
  
          //
          // copy all the OIL entries minus the stats
          //
  
          pleHead = &pse-> leMfeIfList;
          
          for ( ple = pleHead-> Flink, dwInd = 0; 
                ple != pleHead; 
                ple = ple-> Flink, dwInd++ )
          {
              poie = CONTAINING_RECORD( ple, OUT_IF_ENTRY, leIfList );
  
              pmimm-> rgmioOutInfo[ dwInd ].dwOutIfIndex = 
                  poie-> dwIfIndex;
                  
              pmimm-> rgmioOutInfo[ dwInd ].dwNextHopAddr = 
                  poie-> dwIfNextHopAddr;
          }        
      }
  }
  