Newer
Older
bremer-ios-app / Pods / Realm / core / realm-monorepo.xcframework / macos-x86_64_arm64 / Headers / realm / util / encrypted_file_mapping.hpp
/*************************************************************************
 *
 * Copyright 2016 Realm Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 **************************************************************************/

#ifndef REALM_UTIL_ENCRYPTED_FILE_MAPPING_HPP
#define REALM_UTIL_ENCRYPTED_FILE_MAPPING_HPP

#include <realm/util/file.hpp>
#include <realm/util/features.h>
#include <realm/util/aes_cryptor.hpp>

#if REALM_ENABLE_ENCRYPTION

typedef size_t (*Header_to_size)(const char* addr);

#include <vector>

namespace realm::util {

struct SharedFileInfo;

class EncryptedFileMapping {
public:
    // Adds the newly-created object to file.mappings iff it's successfully constructed
    EncryptedFileMapping(SharedFileInfo& file, size_t file_offset, void* addr, size_t size, File::AccessMode access,
                         util::WriteObserver* observer = nullptr, util::WriteMarker* marker = nullptr);
    ~EncryptedFileMapping();

    // Default implementations of copy/assign can trigger multiple destructions
    EncryptedFileMapping(const EncryptedFileMapping&) = delete;
    EncryptedFileMapping& operator=(const EncryptedFileMapping&) = delete;

    // Encrypt all dirty pages, push them to shared cache and mark them read-only
    // Does not call fsync
    void flush() noexcept;

    // Sync the image of this file in shared cache to disk. Does not imply flush.
    void sync() noexcept;

    // Make sure that memory in the specified range is synchronized with any
    // changes made globally visible through call to write_barrier or refresh_outdated_pages().
    // Optionally mark the pages for later modification
    void read_barrier(const void* addr, size_t size, Header_to_size header_to_size, bool to_modify);

    // Ensures that any changes made to memory in the specified range
    // becomes visible to any later calls to read_barrier()
    // Pages selected must have been marked for modification at an earlier read barrier
    void write_barrier(const void* addr, size_t size) noexcept;

    // Mark pages for later checks of the ivs on disk. If the IVs have changed compared to
    // the in memory versions the page will later need to be refreshed.
    // This is the process by which a reader in a multiprocess scenario detects if its
    // mapping should be refreshed while advancing versions.
    // The pages marked for IV-checks will be refetched and re-decrypted by later calls to read_barrier.
    void mark_pages_for_IV_check();

    // Set this mapping to a new address and size
    // Flushes any remaining dirty pages from the old mapping
    void set(void* new_addr, size_t new_size, size_t new_file_offset);

    // Extend the size of this mapping. Memory holding decrypted pages must
    // have been allocated earlier
    void extend_to(size_t offset, size_t new_size);

    size_t collect_decryption_count()
    {
        return m_num_decrypted;
    }
    // reclaim any untouched pages - this is thread safe with respect to
    // concurrent access/touching of pages - but must be called with the mutex locked.
    void reclaim_untouched(size_t& progress_ptr, size_t& accumulated_savings) noexcept;

    bool contains_page(size_t page_in_file) const;
    size_t get_local_index_of_address(const void* addr, size_t offset = 0) const;
    size_t get_offset_of_address(const void* addr) const;

    size_t get_end_index()
    {
        return m_first_page + m_page_state.size();
    }
    size_t get_start_index()
    {
        return m_first_page;
    }
    void set_marker(WriteMarker* marker)
    {
        m_marker = marker;
    }
    void set_observer(WriteObserver* observer)
    {
        m_observer = observer;
    }

private:
    SharedFileInfo& m_file;

    size_t m_page_shift;
    size_t m_blocks_per_page;

    void* m_addr = nullptr;

    size_t m_first_page;
    size_t m_num_decrypted; // 1 for every page decrypted

    enum PageState {
        Clean = 0,
        Touched = 1,  // a ref->ptr translation has taken place
        UpToDate = 2, // the page is fully up to date
        StaleIV = 4,  // the page needs to check the on disk IV for changes by other processes
        Writable = 8, // the page is open for writing
        Dirty = 16    // the page has been modified with respect to what's on file.
    };
    std::vector<PageState> m_page_state;
    // little helpers:
    inline void clear(PageState& ps, int p)
    {
        ps = PageState(ps & ~p);
    }
    inline bool is_not(PageState& ps, int p)
    {
        return (ps & p) == 0;
    }
    inline bool is(PageState& ps, int p)
    {
        return (ps & p) != 0;
    }
    inline void set(PageState& ps, int p)
    {
        ps = PageState(ps | p);
    }
    // 1K pages form a chunk - this array allows us to skip entire chunks during scanning
    std::vector<bool> m_chunk_dont_scan;
    static constexpr int page_to_chunk_shift = 10;
    static constexpr size_t page_to_chunk_factor = size_t(1) << page_to_chunk_shift;

    File::AccessMode m_access;
    util::WriteObserver* m_observer = nullptr;
    util::WriteMarker* m_marker = nullptr;
#ifdef REALM_DEBUG
    std::unique_ptr<char[]> m_validate_buffer;
#endif

    char* page_addr(size_t local_page_ndx) const noexcept;

    void mark_outdated(size_t local_page_ndx) noexcept;
    bool copy_up_to_date_page(size_t local_page_ndx) noexcept;
    void refresh_page(size_t local_page_ndx, size_t required);
    void write_and_update_all(size_t local_page_ndx, size_t begin_offset, size_t end_offset) noexcept;
    void reclaim_page(size_t page_ndx);
    void validate_page(size_t local_page_ndx) noexcept;
    void validate() noexcept;
};

inline size_t EncryptedFileMapping::get_offset_of_address(const void* addr) const
{
    return reinterpret_cast<size_t>(addr) & ((1 << m_page_shift) - 1);
}

inline size_t EncryptedFileMapping::get_local_index_of_address(const void* addr, size_t offset) const
{
    REALM_ASSERT_EX(addr >= m_addr, addr, m_addr);

    size_t local_ndx =
        ((reinterpret_cast<uintptr_t>(addr) - reinterpret_cast<uintptr_t>(m_addr) + offset) >> m_page_shift);
    REALM_ASSERT_EX(local_ndx < m_page_state.size(), local_ndx, m_page_state.size(), size_t(addr), size_t(m_addr),
                    m_page_shift);
    return local_ndx;
}

inline bool EncryptedFileMapping::contains_page(size_t page_in_file) const
{
    // first check for (page_in_file >= m_first_page) so that the following
    // subtraction using unsigned types never wraps under 0
    return page_in_file >= m_first_page && page_in_file - m_first_page < m_page_state.size();
}

} // namespace realm::util

#endif // REALM_ENABLE_ENCRYPTION

namespace realm::util {
/// Thrown by EncryptedFileMapping if a file opened is non-empty and does not
/// contain valid encrypted data
struct DecryptionFailed : FileAccessError {
    DecryptionFailed()
        : FileAccessError(ErrorCodes::DecryptionFailed, "Decryption failed", std::string(), 0)
    {
    }
    DecryptionFailed(const std::string& msg)
        : FileAccessError(ErrorCodes::DecryptionFailed, util::format("Decryption failed: '%1'", msg), std::string())
    {
    }
};
} // namespace realm::util

#endif // REALM_UTIL_ENCRYPTED_FILE_MAPPING_HPP