winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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, &currentPageOffset));
     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, &currentPageOffset));
     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, &currentBufferIndex));
    108             winrt::check_hresult(ULong64Add(currentPageOffset, PAGE_SIZE, &currentPageOffset));
    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 }