/*++ Copyright (c) 1996 Microsoft Corp. Module Name: hashutil.cxx Abstract: Implementation of linked list of hash tables for cache index lookup. Author: Rajeev Dujari (rajeevd) 22-Oct-96 --*/ #include #include #define SIG_HASH ('H'|('A'<<8)|('S'<<16)|('H'<<24)) typedef LIST_FILEMAP_ENTRY HASH_FILEMAP_ENTRY; // hash table parameters #define BYTES_PER_PAGE 4096 #define ITEMS_PER_BUCKET ((BYTES_PER_PAGE - sizeof(HASH_FILEMAP_ENTRY))\ / (SLOT_COUNT * sizeof(HASH_ITEM))) #define BYTES_PER_TABLE (sizeof(HASH_FILEMAP_ENTRY) \ + SLOT_COUNT * ITEMS_PER_BUCKET * sizeof(HASH_ITEM)) // // Hash Function: Pearson's method // PRIVATE DWORD HashKey (LPCSTR lpsz, DWORD dwAddedHash) { union { DWORD dw; BYTE c[4]; } Hash, Hash2; const static BYTE bTranslate[256] = { 1, 14,110, 25, 97,174,132,119,138,170,125,118, 27,233,140, 51, 87,197,177,107,234,169, 56, 68, 30, 7,173, 73,188, 40, 36, 65, 49,213,104,190, 57,211,148,223, 48,115, 15, 2, 67,186,210, 28, 12,181,103, 70, 22, 58, 75, 78,183,167,238,157,124,147,172,144, 176,161,141, 86, 60, 66,128, 83,156,241, 79, 46,168,198, 41,254, 178, 85,253,237,250,154,133, 88, 35,206, 95,116,252,192, 54,221, 102,218,255,240, 82,106,158,201, 61, 3, 89, 9, 42,155,159, 93, 166, 80, 50, 34,175,195,100, 99, 26,150, 16,145, 4, 33, 8,189, 121, 64, 77, 72,208,245,130,122,143, 55,105,134, 29,164,185,194, 193,239,101,242, 5,171,126, 11, 74, 59,137,228,108,191,232,139, 6, 24, 81, 20,127, 17, 91, 92,251,151,225,207, 21, 98,113,112, 84,226, 18,214,199,187, 13, 32, 94,220,224,212,247,204,196, 43, 249,236, 45,244,111,182,153,136,129, 90,217,202, 19,165,231, 71, 230,142, 96,227, 62,179,246,114,162, 53,160,215,205,180, 47,109, 44, 38, 31,149,135, 0,216, 52, 63, 23, 37, 69, 39,117,146,184, 163,200,222,235,248,243,219, 10,152,131,123,229,203, 76,120,209 }; // Seed the hash values based on the first character. Hash.c[0] = bTranslate[ *lpsz]; Hash.c[1] = bTranslate[(*lpsz+1) & 255]; Hash.c[2] = bTranslate[(*lpsz+2) & 255]; Hash.c[3] = bTranslate[(*lpsz+3) & 255]; Hash.dw += dwAddedHash; while (*++lpsz) { // Allow URLs differing only by trailing slash to collide. if (lpsz[0] == '/' && lpsz[1] == 0) break; Hash2.c[0] = Hash.c[0] ^ *lpsz; Hash2.c[1] = Hash.c[1] ^ *lpsz; Hash2.c[2] = Hash.c[2] ^ *lpsz; Hash2.c[3] = Hash.c[3] ^ *lpsz; Hash.c[0] = bTranslate[Hash2.c[0]]; Hash.c[1] = bTranslate[Hash2.c[1]]; Hash.c[2] = bTranslate[Hash2.c[2]]; Hash.c[3] = bTranslate[Hash2.c[3]]; } return Hash.dw; } // // HashLookupItem support functions specific to urlcache: // AllocTable // IsMatch // PRIVATE HASH_FILEMAP_ENTRY* AllocTable (LPVOID pAllocObj, LPBYTE* ppBase, LPDWORD* ppdwOffset) { // Save the offset to the table offset. DWORD_PTR dpOffsetToTableOffset = (LPBYTE)*ppdwOffset - *ppBase; // 64BIT // Ask for BYTES_PER_PAGE instead of BYTES_PER_TABLE // so the allocator knows to align on a page boundary. INET_ASSERT (BYTES_PER_PAGE >= BYTES_PER_TABLE); MEMMAP_FILE* pmmf = (MEMMAP_FILE*) pAllocObj; HASH_FILEMAP_ENTRY* pTable = (HASH_FILEMAP_ENTRY *) pmmf->AllocateEntry (BYTES_PER_PAGE); if (!pTable) return NULL; INET_ASSERT (! (((LPBYTE) pTable - *pmmf->GetHeapStart()) & (BYTES_PER_PAGE-1)) ); // Chain new table to previous table. *ppBase = *pmmf->GetHeapStart(); *ppdwOffset = (DWORD*) (*ppBase + dpOffsetToTableOffset); **ppdwOffset = (DWORD) ((LPBYTE)pTable - *ppBase); // 64BIT // Initialize the header. pTable->dwSig = SIG_HASH; pTable->dwNext = 0; // Fill the rest of the entry with HASH_END DWORD* pdw = (DWORD *) (pTable + 1); DWORD cdw = SLOT_COUNT * ITEMS_PER_BUCKET * (sizeof(HASH_ITEM)/sizeof(DWORD)); INET_ASSERT (!(sizeof(HASH_ITEM) % sizeof(DWORD))); while (cdw--) *pdw++ = HASH_END; // Return the new table. return pTable; } // // IsMatch: determine if hash table item with a matching 32-bit hash value // is an actual match or return NULL if a collision. // PRIVATE HASH_ITEM* URL_CONTAINER::IsMatch (HASH_ITEM *pItem, LPCSTR pszKey, DWORD dwFlags) { MEMMAP_FILE* pmmf = _UrlObjStorage; dwFlags &= (LOOKUP_BIT_REDIR | LOOKUP_BIT_CREATE); if (pmmf->IsBadOffset (pItem->dwOffset)) { // Fix up a bad hash table item. This could happen if a thread // died between allocating a hash table item and setting the offset. pItem->MarkFree(); return NULL; } FILEMAP_ENTRY* pEntry = (FILEMAP_ENTRY*) (*pmmf->GetHeapStart() + pItem->dwOffset); switch (pEntry->dwSig) { case SIG_URL: { // Fail if lookup flags are inconsistent with url entry type. INET_ASSERT (!(pItem->dwHash & HASH_BIT_NOTURL)); // Get pointer to URL. URL_FILEMAP_ENTRY *pUrlEntry = (URL_FILEMAP_ENTRY *) pEntry; LPSTR pszUrl = ((LPSTR) pUrlEntry) + pUrlEntry->UrlNameOffset; LPCSTR pszKey2 = pszKey, pszUrl2 = pszUrl; while ( *pszKey2 && *pszUrl2 && *pszKey2 == *pszUrl2 ) { pszKey2++; pszUrl2++; } if (!*pszKey2 && ! *pszUrl2) { // Found exact match. if (dwFlags == LOOKUP_REDIR_CREATE) { // We are have a cache entry for a URL which is now // redirecting. Delete the cache entry. DeleteUrlEntry (pUrlEntry, pItem, SIG_DELETE); return NULL; } return pItem; } // If redirects allowed, check for trailing slash match. if ((dwFlags == LOOKUP_URL_TRANSLATE) && (pItem->dwHash & HASH_BIT_REDIR)) { DWORD cbUrl = strlen (pszUrl); DWORD cbKey = strlen (pszKey); INET_ASSERT (cbUrl && pszUrl[cbUrl - 1] == '/'); if (cbUrl == (cbKey + 1) && !memcmp (pszUrl, pszKey, cbKey)) return pItem; } return NULL; } case SIG_REDIR: { // When online, filter out offline redirect entries. if (dwFlags == LOOKUP_URL_NOCREATE) return NULL; // Check that redirect URL matches exactly. REDIR_FILEMAP_ENTRY* pRedir = (REDIR_FILEMAP_ENTRY *) pEntry; if (lstrcmp (pszKey, pRedir->szUrl)) return NULL; switch (dwFlags) { case LOOKUP_URL_CREATE: // We are creating a new entry for a URL that once // redirected. Delete the stale redirect entry. pmmf->FreeEntry (pRedir); pItem->MarkFree(); return NULL; case LOOKUP_REDIR_CREATE: // Return the redirect item if we're looking for it. return pItem; case LOOKUP_URL_TRANSLATE: // Otherwise, translate through the redirect item. pItem = (HASH_ITEM *) (*pmmf->GetHeapStart() + pRedir->dwItemOffset); // Perform some consistency checks. if (pItem->dwHash & HASH_BIT_NOTURL) return NULL; // not an URL entry if ((pItem->dwHash & ~SLOT_MASK) != pRedir->dwHashValue) return NULL; // not a matching URL entry return pItem; default: INET_ASSERT (FALSE); } } default: { // Fix up a bad hash table entry. This can happen if a thread // died between allocating a hash table item and setting the offset. pItem->MarkFree(); return NULL; } } } // // HashFindItem: finds a matching entry or else the first free slot // BOOL URL_CONTAINER::HashFindItem (LPCSTR pszKey, DWORD dwFlags, HASH_ITEM** ppItem) { INET_ASSERT(!((dwFlags & LOOKUP_URL_DONT_FOLLOW) && (dwFlags & LOOKUP_BIT_CREATE))); DWORD dwFind = 0; BOOL fLookAgain = !(dwFlags & LOOKUP_URL_DONT_FOLLOW) && GlobalIdentity; again: LPVOID pAllocObj = (LPVOID) _UrlObjStorage; LPBYTE pBase = *_UrlObjStorage->GetHeapStart(); LPDWORD pdwTableOffset = _UrlObjStorage->GetPtrToHashTableOffset(); // Scan flags. BOOL fCreate = dwFlags & LOOKUP_BIT_CREATE; HASH_ITEM* pFree = NULL; DWORD nBlock = 0; // Hash the URL and calculate the slot. DWORD dwHash = HashKey(pszKey, dwFind); DWORD iSlot = dwHash & SLOT_MASK; dwHash &= ~SLOT_MASK; // Walk through the list of hash tables. while (*pdwTableOffset && !_UrlObjStorage->IsBadOffset(*pdwTableOffset)) { // Calculate offset to next hash table and validate signature. HASH_FILEMAP_ENTRY* pTable = (HASH_FILEMAP_ENTRY*) (pBase + *pdwTableOffset); if (pTable->dwSig != SIG_HASH || pTable->nBlock != nBlock++) break; // Calculate offset to bucket in this table. HASH_ITEM* pItem = ((HASH_ITEM*) (pTable + 1)) + iSlot * ITEMS_PER_BUCKET; // Scan the bucket. for (DWORD iSeat=0; iSeatdwHash & HASH_BIT_RESERVED)); switch (pItem->dwHash) { case HASH_FREE: // free item but more items might follow { INET_ASSERT (!(pItem->dwHash & ~SLOT_MASK)); // If caller wants a free item, record the first one we find. if (!pFree && fCreate) pFree = pItem; } continue; case HASH_END: // first previously unused free item; no more to follow { INET_ASSERT (!(pItem->dwHash & ~SLOT_MASK)); if (!fCreate) *ppItem = NULL; else { // Hand out the first free slot. if (pFree) { // Invalidate offset in case caller neglects to set it. pFree->dwOffset = HASH_END; *ppItem = pFree; } else { // The first free slot has never been used before. INET_ASSERT (pItem->dwOffset == HASH_END); *ppItem = pItem; } (*ppItem)->dwHash = dwHash; } } return FALSE; default: { // Check if the key matches. if (dwHash == (pItem->dwHash & ~SLOT_MASK)) { if (dwFlags & INTERNET_CACHE_FLAG_ALLOW_COLLISIONS) { *ppItem = pItem; return TRUE; } HASH_ITEM* pItem2 = IsMatch(pItem, pszKey, dwFlags); if (pItem2) { LPURL_FILEMAP_ENTRY pEntry = (URL_FILEMAP_ENTRY*)(*(((MEMMAP_FILE*)_UrlObjStorage)->GetHeapStart()) + pItem2->dwOffset); // This will check for a ~U: header or IDENTITY_CACHE_ENTRY // We use the first for compatibility with Wininet5 // We use IDENTITY_CACHE_ENTRY for entries we wish to hide from Wininet5 if ((pEntry->dwSig==SIG_URL) && (!(dwFlags & LOOKUP_URL_DONT_FOLLOW) && ((pEntry->CacheEntryType & IDENTITY_CACHE_ENTRY) || IsPerUserEntry(pEntry)))) { // We'll search again for an entry corresponding to GlobalIdentity if (fLookAgain) { fLookAgain = FALSE; dwFind = GlobalIdentity; goto again; } // Guarantee that this is what we want if (pEntry->GetIdentity()!=GlobalIdentity) { continue; } // If we're looking for an identity-0 cache entry // and there is no filename, we need to do the following: // 1. if we're trying to create an entry, then return this // 2. otherwise, say No, the entry is not present if (!pEntry->InternalFileNameOffset && !fCreate) { *ppItem = NULL; return FALSE; } } *ppItem = pItem2; return TRUE; } } } continue; } // end switch } // end for loop to scan seats in bucket // Follow the link to the next table. pdwTableOffset = &pTable->dwNext; } // end while (*pdwTableOffset) // If we've encountered a corrupt table, we'll have to recover if (*pdwTableOffset) { INET_ASSERT(FALSE); *pdwTableOffset = 0; } // We are out a buckets, so an item hasn't been found. if (fCreate && !pFree) { // Caller wanted a free item but we didn't find one. HASH_FILEMAP_ENTRY* pTable = AllocTable (pAllocObj, &pBase, &pdwTableOffset); ////////////////////////////////////////////////////////////////////// // WARNING: the file might have grown and remapped, so any pointers // // hereafter must be recalculated by offsets from the new base. // ////////////////////////////////////////////////////////////////////// if (pTable) { pTable->nBlock = nBlock; // Calculate next free slot. pFree = ((HASH_ITEM*) (pTable + 1)) + iSlot * ITEMS_PER_BUCKET; INET_ASSERT (pFree->dwHash == HASH_END); INET_ASSERT (pFree->dwOffset == HASH_END); } } // Return free item if desired and indicate no item found. if (pFree) { INET_ASSERT (fCreate); pFree->dwHash = dwHash; pFree->dwOffset = HASH_END; // invalid in case caller neglects to set it } *ppItem = pFree; return FALSE; } // // HashFindNextItem: scans the table for the next valid URL item // PUBLIC HASH_ITEM* HashGetNextItem ( IN LPVOID pAllocObj, // allocator object IN LPBYTE pBase, // base for all offsets IN OUT LPDWORD pdwItemOffset, // current item offset IN DWORD dwFlags // include redirects? ) { INET_ASSERT (!(dwFlags & ~LOOKUP_BIT_REDIR)); // Check if there if the hash table is empty (or we are at the end already.) if (!*pdwItemOffset) return NULL; HASH_ITEM* pItem = (HASH_ITEM*) (pBase + *pdwItemOffset); // Calculate current table offset, assuming it's the previous page boundary. INET_ASSERT (BYTES_PER_TABLE <= BYTES_PER_PAGE); HASH_FILEMAP_ENTRY* pTable = (HASH_FILEMAP_ENTRY*) (((DWORD_PTR)pItem) & ~(BYTES_PER_PAGE - 1)); // Advance item pointer to next location. if (pItem == (HASH_ITEM*) pTable) pItem = (HASH_ITEM*) (pTable + 1); // first location in table else pItem++; // next location in table do // Scan the list of tables. { if (pTable->dwSig != SIG_HASH) break; // Scan the current table. for (; (LPBYTE) pItem < ((LPBYTE) pTable) + BYTES_PER_TABLE; pItem++) { // No reserved bits should be set. INET_ASSERT (!(pItem->dwHash & HASH_BIT_RESERVED)); if (!(pItem->dwHash & HASH_BIT_NOTURL) || (dwFlags /* & LOOKUP_BIT_REDIR */) && ((pItem->dwHash & HASH_FLAG_MASK) == HASH_REDIR)) { // Found a valid entry. *pdwItemOffset = (DWORD) ((LPBYTE)pItem - pBase); // 64BIT return pItem; } } // Follow the link to the next table. if (!pTable->dwNext) pTable = NULL; else { // Validate the table signature and sequence number. DWORD nBlock = pTable->nBlock; pTable = (HASH_FILEMAP_ENTRY*) (pBase + pTable->dwNext); if (pTable->dwSig != SIG_HASH || pTable->nBlock != nBlock + 1) pTable = NULL; // Set pointer to first location in table. pItem = (HASH_ITEM*) (pTable + 1); } } while (pTable); // We reached the end of the last table. *pdwItemOffset = 0; return NULL; }