HttpLocalCache.cpp (10239B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 4 #include "pch.h" 5 #include "HttpLocalCache.h" 6 7 using namespace Windows::Storage::Streams; 8 using namespace winrt::Windows::Storage::Streams; 9 using namespace winrt::Windows::Security::Cryptography; 10 11 // Note: this class is used by the HttpRandomAccessStream which is passed to the AppxPackaging COM API 12 // All exceptions thrown across dll boundaries should be WinRT exception not custom exceptions. 13 // The HRESULTs will be mapped to UI error code by the appropriate component 14 namespace AppInstaller::Utility::HttpStream 15 { 16 std::future<IBuffer> HttpLocalCache::ReadFromCacheAndDownloadIfNecessaryAsync( 17 const ULONG64 requestedPosition, 18 const UINT32 requestedSize, 19 HttpClientWrapper* httpClientWrapper, 20 InputStreamOptions httpInputStreamOptions) 21 { 22 // Increment cache access counter user for implementing LRU replacement 23 m_accessCounter++; 24 25 // Find all the pages for the given request, and the pages that are missing 26 std::vector<ULONG64> allPages; 27 std::vector<ULONG64> unsatisfiablePages; 28 FindCachePages(requestedPosition, requestedSize, allPages, unsatisfiablePages); 29 30 // download the missing pages 31 co_await DownloadAndSaveToCacheAsync( 32 unsatisfiablePages, 33 httpClientWrapper, 34 httpInputStreamOptions); 35 36 // At this point, everything should be in the cache 37 IBuffer constructedBuffer = {}; 38 39 for (UINT32 i = 0; i < allPages.size(); i++) 40 { 41 UINT64 pageOffset = allPages[i]; 42 IBuffer cachedPageBuffer = ReadPageFromCache(pageOffset); 43 constructedBuffer = ConcatenateBuffers(constructedBuffer, cachedPageBuffer); 44 } 45 46 // trim buffer to match requested range 47 IBuffer requestedBuffer = TrimBufferToSatisfyRequest( 48 constructedBuffer, 49 requestedPosition, 50 requestedSize, 51 allPages); 52 53 VacateStaleEntriesFromCache(); 54 55 co_return requestedBuffer; 56 } 57 58 void HttpLocalCache::FindCachePages( 59 ULONG64 requestedPosition, 60 UINT32 requestedSize, 61 std::vector<ULONG64>& allPages, 62 std::vector<ULONG64>& unsatisfiablePages) 63 { 64 ULONG64 requestedEndPosition; 65 ULONG64 currentPageOffset; 66 winrt::check_hresult(ULong64Add(requestedPosition, requestedSize, &requestedEndPosition)); 67 winrt::check_hresult(ULong64Mult((requestedPosition / PAGE_SIZE), PAGE_SIZE, ¤tPageOffset)); 68 69 // There's always at least one page for the range 70 do 71 { 72 allPages.push_back(currentPageOffset); 73 74 if (m_localCache.find(currentPageOffset) == m_localCache.end()) 75 { 76 unsatisfiablePages.push_back(currentPageOffset); 77 } 78 79 winrt::check_hresult(ULong64Add(currentPageOffset, PAGE_SIZE, ¤tPageOffset)); 80 81 } while (currentPageOffset < requestedEndPosition); 82 } 83 84 // Breaks the provided buffer into smaller buffers and saves them to the cache at the corresponding 85 // page offset position, starting at firstPageOffset. The smaller buffers are all PAGE_SIZE bytes, 86 // except for the one corresponding to the last page in the file 87 void HttpLocalCache::SaveBufferToCache(const IBuffer& buffer, const ULONG64 firstPageOffset) 88 { 89 UINT32 remainingBufferSize = buffer.Length(); 90 UINT32 currentBufferIndex = 0; 91 ULONG64 currentPageOffset = firstPageOffset; 92 93 while (remainingBufferSize > 0) 94 { 95 // Extract the sub-buffer 96 UINT32 currentPageSize = std::min(remainingBufferSize, PAGE_SIZE); 97 IBuffer currentPageBuffer = CreateTrimmedBuffer(buffer, currentBufferIndex, currentPageSize); 98 99 // Add it to the cache 100 CachedPage currentPage; 101 currentPage.lastAccessCounter = m_accessCounter; 102 currentPage.buffer = currentPageBuffer; 103 m_localCache[currentPageOffset] = currentPage; 104 105 // update loop vars 106 winrt::check_hresult(UInt32Sub(remainingBufferSize, currentPageSize, &remainingBufferSize)); 107 winrt::check_hresult(UInt32Add(currentBufferIndex, currentPageSize, ¤tBufferIndex)); 108 winrt::check_hresult(ULong64Add(currentPageOffset, PAGE_SIZE, ¤tPageOffset)); 109 } 110 } 111 112 IBuffer HttpLocalCache::ReadPageFromCache(const ULONG64 pageOffset) 113 { 114 if (!(m_localCache.find(pageOffset) != m_localCache.end())) 115 { 116 THROW_HR(E_INVALIDARG); 117 } 118 119 CachedPage& page = m_localCache[pageOffset]; 120 page.lastAccessCounter = m_accessCounter; 121 122 return page.buffer; 123 } 124 125 // Trims a buffer that was constructed (by fetching pages from cache and downloading missing pages) 126 // in order to satisfy a request and return the exact buffer the consumer asked for. 127 IBuffer HttpLocalCache::TrimBufferToSatisfyRequest( 128 const IBuffer& constructedBuffer, 129 const ULONG64 requestedPosition, 130 const UINT32 requestedSize, 131 const std::vector<ULONG64> allPages) 132 { 133 ULONG64 fullBufferStartOffset = allPages[0]; 134 135 ULONG64 trimmedBufferStartRelativeIndex; 136 winrt::check_hresult(ULong64Sub(requestedPosition, fullBufferStartOffset, &trimmedBufferStartRelativeIndex)); 137 138 IBuffer requestedBuffer = CreateTrimmedBuffer( 139 constructedBuffer, 140 (UINT32)trimmedBufferStartRelativeIndex, // Conversion is safe as buffer size is a UINT32. 141 requestedSize); 142 143 return requestedBuffer; 144 } 145 146 // Downloads a chunk of the file, saves it to the cache, and returns the corresponding buffer 147 // If the requested size is 0, this method returns an empty buffer without making HTTP calls 148 std::future<void> HttpLocalCache::DownloadAndSaveToCacheAsync( 149 const std::vector<ULONG64> unsatisfiablePages, 150 HttpClientWrapper* httpClientWrapper, 151 InputStreamOptions httpInputStreamOptions) 152 { 153 // Determine the download job 154 // To make things easy, we will download the contiguous range that includes all the unsatisfiable ranges. 155 // Note that in theory, this may include cached pages. However, this situation is rarely expected to happen, 156 // if at all. The package reader usually reads things in chunks of 64 KB or less, so, we should expect to 157 // always have up to two satisfiable and unsatisfiable pages in total. 158 UINT64 fileSize = httpClientWrapper->GetFullFileSize(); 159 ULONG64 downloadJobStartPosition = 0U; 160 ULONG64 downloadJobEndPosition = 0U; 161 ULONG64 downloadJobSize = 0U; 162 if (unsatisfiablePages.size() > 0U) 163 { 164 downloadJobStartPosition = unsatisfiablePages[0]; 165 ULONG64 lastUnsatisfiableJob = unsatisfiablePages[unsatisfiablePages.size() - 1]; 166 winrt::check_hresult(ULong64Add(lastUnsatisfiableJob, PAGE_SIZE, &downloadJobEndPosition)); 167 168 // make sure to not overflow file size 169 downloadJobEndPosition = std::min(downloadJobEndPosition, fileSize); 170 winrt::check_hresult(ULong64Sub(downloadJobEndPosition, downloadJobStartPosition, &downloadJobSize)); 171 } 172 173 if (downloadJobSize != 0U) 174 { 175 // start download job 176 IBuffer downloadedBuffer = co_await httpClientWrapper->DownloadRangeAsync( 177 downloadJobStartPosition, 178 (UINT32)downloadJobSize, 179 httpInputStreamOptions); 180 181 SaveBufferToCache(downloadedBuffer, downloadJobStartPosition); 182 } 183 } 184 185 void HttpLocalCache::VacateStaleEntriesFromCache() 186 { 187 // Copy page offsets into vector and sort by the access counter 188 std::vector<std::pair<UINT64, int>> orderedPageOffsets; 189 for (auto pageIter = m_localCache.begin(); pageIter != m_localCache.end(); pageIter++) 190 { 191 orderedPageOffsets.push_back(std::pair<UINT64, int>(pageIter->first, pageIter->second.lastAccessCounter)); 192 } 193 194 // Compare function to sort by access counter 195 auto cmp = [](std::pair<UINT64, int> const & a, std::pair<UINT64, int> const & b) 196 { 197 return a.second != b.second ? a.second < b.second : a.first < b.first; 198 }; 199 200 std::sort(orderedPageOffsets.begin(), orderedPageOffsets.end(), cmp); 201 202 for (auto pageIter = orderedPageOffsets.begin(); pageIter != orderedPageOffsets.end(); pageIter++) 203 { 204 if (m_localCache.size() > MAX_PAGES) 205 { 206 m_localCache.erase(pageIter->first); 207 } 208 else 209 { 210 break; 211 } 212 } 213 } 214 215 IBuffer HttpLocalCache::CreateTrimmedBuffer( 216 const IBuffer& originalBuffer, 217 UINT32 trimStartIndex, 218 UINT32 size) 219 { 220 uint32_t bufferLength = originalBuffer.Length(); 221 THROW_HR_IF(E_INVALIDARG, trimStartIndex > bufferLength); 222 223 originalBuffer.as<::IInspectable>(); 224 225 // Get the byte array from the IBuffer object 226 Microsoft::WRL::ComPtr<IBufferByteAccess> bufferByteAccess; 227 ::IInspectable* bufferAbi = (::IInspectable*)winrt::get_abi(originalBuffer); 228 bufferAbi->QueryInterface(IID_PPV_ARGS(&bufferByteAccess)); 229 byte* byteBuffer = nullptr; 230 bufferByteAccess->Buffer(&byteBuffer); 231 232 // Create the array of bytes holding the trimmed bytes 233 IBuffer trimmedBuffer = CryptographicBuffer::CreateFromByteArray( 234 { byteBuffer + trimStartIndex, std::min(size, bufferLength - trimStartIndex) }); 235 236 return trimmedBuffer; 237 } 238 239 IBuffer HttpLocalCache::ConcatenateBuffers(const IBuffer& buffer1, const IBuffer& buffer2) 240 { 241 DataWriter writer; 242 writer.WriteBuffer(buffer1); 243 writer.WriteBuffer(buffer2); 244 return writer.DetachBuffer(); 245 } 246 }