Unverified Commit 23c1cdb1 authored by Zach Knox's avatar Zach Knox
Browse files

notifications work—still work to do but it's progress

also updated pods
parent fa006df2
......@@ -76,11 +76,21 @@ typedef void(^RLMSyncErrorReportingBlock)(NSError *, RLMSyncSession * _Nullable)
@property (nullable, nonatomic, copy) RLMSyncErrorReportingBlock errorHandler;
/**
A reverse-DNS string uniquely identifying this application. In most cases this is automatically set by the SDK, and
does not have to be explicitly configured.
A reverse-DNS string uniquely identifying this application. In most cases this
is automatically set by the SDK, and does not have to be explicitly configured.
*/
@property (nonatomic, copy) NSString *appID;
/**
A string identifying this application which is included in the User-Agent
header of sync connections. By default, this will be the application's bundle
identifier.
This property must be set prior to opening a synchronized Realm for the first
time. Any modifications made after opening a Realm will be ignored.
*/
@property (nonatomic, copy) NSString *userAgent;
/**
The logging threshold which newly opened synced Realms will use. Defaults to
`RLMSyncLogLevelInfo`.
......@@ -103,6 +113,31 @@ typedef void(^RLMSyncErrorReportingBlock)(NSError *, RLMSyncSession * _Nullable)
*/
@property (nullable, nonatomic, copy) NSDictionary<NSString *, NSString *> *customRequestHeaders;
/**
A map of hostname to file URL for pinned certificates to use for HTTPS requests.
When initiating a HTTPS connection to a server, if this dictionary contains an
entry for the server's hostname, only the certificates stored in the file (or
any certificates signed by it, if the file contains a CA cert) will be accepted
when initiating a connection to a server. This prevents certain certain kinds
of man-in-the-middle (MITM) attacks, and can also be used to trust a self-signed
certificate which would otherwise be untrusted.
On macOS, the certificate files may be in any of the formats supported by
SecItemImport(), including PEM and .cer (see SecExternalFormat for a complete
list of possible formats). On iOS and other platforms, only DER .cer files are
supported.
For example, to pin example.com to a .cer file included in your bundle:
<pre>
RLMSyncManager.sharedManager.pinnedCertificatePaths = @{
@"example.com": [NSBundle.mainBundle pathForResource:@"example.com" ofType:@"cer"]
};
</pre>
*/
@property (nullable, nonatomic, copy) NSDictionary<NSString *, NSURL *> *pinnedCertificatePaths;
/// The sole instance of the singleton.
+ (instancetype)sharedManager NS_REFINED_FOR_SWIFT;
......
......@@ -24,14 +24,33 @@
The current state of the session represented by a session object.
*/
typedef NS_ENUM(NSUInteger, RLMSyncSessionState) {
/// The sync session is bound to the Realm Object Server and communicating with it.
/// The sync session is actively communicating or attempting to communicate
/// with the Realm Object Server. A session is considered Active even if
/// it is not currently connected. Check the connection state instead if you
/// wish to know if the connection is currently online.
RLMSyncSessionStateActive,
/// The sync session is not currently communicating with the Realm Object Server.
/// The sync session is not attempting to communicate with the Realm Object
/// Server, due to the user logging out or synchronization being paused.
RLMSyncSessionStateInactive,
/// The sync session encountered a fatal error and is permanently invalid; it should be discarded.
RLMSyncSessionStateInvalid
};
/**
The current state of a sync session's connection. Sessions which are not in
the Active state will always be Disconnected.
*/
typedef NS_ENUM(NSUInteger, RLMSyncConnectionState) {
/// The sync session is not connected to the server, and is not attempting
/// to connect, either because the session is inactive or because it is
/// waiting to retry after a failed connection.
RLMSyncConnectionStateDisconnected,
/// The sync session is attempting to connect to the Realm Object Server.
RLMSyncConnectionStateConnecting,
/// The sync session is currently connected to the Realm Object Server.
RLMSyncConnectionStateConnected,
};
/**
The transfer direction (upload or download) tracked by a given progress notification block.
......@@ -105,8 +124,17 @@ NS_ASSUME_NONNULL_BEGIN
@interface RLMSyncSession : NSObject
/// The session's current state.
///
/// This property is not KVO-compliant.
@property (nonatomic, readonly) RLMSyncSessionState state;
/// The session's current connection state.
///
/// This property is KVO-compliant and can be observed to be notified of changes.
/// Be warned that KVO observers for this property may be called on a background
/// thread.
@property (atomic, readonly) RLMSyncConnectionState connectionState;
/// The Realm Object Server URL of the remote Realm this session corresponds to.
@property (nullable, nonatomic, readonly) NSURL *realmURL;
......@@ -119,6 +147,22 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (nullable RLMSyncConfiguration *)configuration;
/**
Temporarily suspend syncronization and disconnect from the server.
The session will not attempt to connect to Realm Object Server until `resume`
is called or the Realm file is closed and re-opened.
*/
- (void)suspend;
/**
Resume syncronization and reconnect to Realm Object Server after suspending.
This is a no-op if the session was already active or if the session is invalid.
Newly created sessions begin in the Active state and do not need to be resumed.
*/
- (void)resume;
/**
Register a progress notification block.
......
......@@ -32,7 +32,7 @@ NS_ASSUME_NONNULL_BEGIN
std::weak_ptr<realm::SyncSession> _session;
} RLM_SYNC_UNINITIALIZABLE
- (instancetype)initWithSyncSession:(std::shared_ptr<realm::SyncSession>)session;
- (instancetype)initWithSyncSession:(std::shared_ptr<realm::SyncSession> const&)session;
/// Wait for pending uploads to complete or the session to expire, and dispatch the callback onto the specified queue.
- (BOOL)waitForUploadCompletionOnQueue:(nullable dispatch_queue_t)queue callback:(void(^)(NSError * _Nullable))callback;
......
......@@ -16,6 +16,7 @@
//
////////////////////////////////////////////////////////////////////////////
#import <Realm/RLMRealm.h>
#import <Realm/RLMResults.h>
NS_ASSUME_NONNULL_BEGIN
......@@ -54,30 +55,38 @@ typedef NS_ENUM(NSInteger, RLMSyncSubscriptionState) {
/**
`RLMSyncSubscription` represents a subscription to a set of objects in a synced Realm.
When partial sync is enabled for a synced Realm, the only objects that the server synchronizes to the
client are those that match a sync subscription registered by that client. A subscription consists of
of a query (represented by an `RLMResults`) and an optional name.
When query-based sync is enabled for a synchronized Realm, the server only
synchronizes objects to the client when they match a sync subscription
registered by that client. A subscription consists of of a query (represented
by an `RLMResults`) and an optional name.
The state of the subscription can be observed using [Key-Value Observing](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html) on the `state` property.
The state of the subscription can be observed using
[Key-Value Observing](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html)
on the `state` property.
Subscriptions are created using `-[RLMResults subscribe]` or `-[RLMResults subscribeWithName:]`.
Subscriptions are created using `-[RLMResults subscribe]` or
`-[RLMResults subscribeWithName:]`. Existing subscriptions for a Realm can be
looked up with `-[RLMRealm subscriptions]` or `-[RLMRealm subscriptionWithName:]`.
*/
@interface RLMSyncSubscription : NSObject
/**
The unique name for this subscription.
This will be `nil` if a name was not provided when the subscription was created.
This will be `nil` if this object was created with `-[RLMResults subscribe]`.
Subscription objects read from a Realm with `-[RLMRealm subscriptions]` will
always have a non-`nil` name and subscriptions which were not explicitly named
will have an automatically generated one.
*/
@property (nonatomic, readonly, nullable) NSString *name;
/**
The state of the subscription. See `RLMSyncSubscriptionState`.
The current state of the subscription. See `RLMSyncSubscriptionState`.
*/
@property (nonatomic, readonly) RLMSyncSubscriptionState state;
/**
The error associated with this subscription, if any.
The error which occurred when registering this subscription, if any.
Will be non-nil only when `state` is `RLMSyncSubscriptionStateError`.
*/
......@@ -86,10 +95,15 @@ typedef NS_ENUM(NSInteger, RLMSyncSubscriptionState) {
/**
Remove this subscription.
Removing a subscription will delete all objects from the local Realm that were matched
only by that subscription and not any remaining subscriptions. The deletion is performed
by the server, and so has no immediate impact on the contents of the local Realm. If the
device is currently offline, the removal will not be processed until the device returns online.
Removing a subscription will delete all objects from the local Realm that were
matched only by that subscription and not any remaining subscriptions. The
deletion is performed by the server, and so has no immediate impact on the
contents of the local Realm. If the device is currently offline, the removal
will not be processed until the device returns online.
Unsubscribing is an asynchronous operation and will not immediately remove the
subscription from the Realm's list of subscriptions. Observe the state property
to be notified of when the subscription has actually been removed.
*/
- (void)unsubscribe;
......@@ -115,9 +129,19 @@ typedef NS_ENUM(NSInteger, RLMSyncSubscriptionState) {
/**
Subscribe to the query represented by this `RLMResults`.
The subscription will not be explicitly named.
Subscribing to a query asks the server to synchronize all objects to the
client which match the query, along with all objects which are reachable
from those objects via links. This happens asynchronously, and the local
client Realm may not immediately have all objects which match the query.
Observe the `state` property of the returned subscription object to be
notified of when the subscription has been processed by the server and
all objects matching the query are available.
@return The subscription
The subscription will not be explicitly named. A name will be automatically
generated for internal use. The exact format of this name may change without
warning and should not be depended on.
@return An object representing the newly-created subscription.
@see RLMSyncSubscription
*/
......@@ -126,13 +150,105 @@ typedef NS_ENUM(NSInteger, RLMSyncSubscriptionState) {
/**
Subscribe to the query represented by this `RLMResults`.
Subscribing to a query asks the server to synchronize all objects to the
client which match the query, along with all objects which are reachable
from those objects via links. This happens asynchronously, and the local
client Realm may not immediately have all objects which match the query.
Observe the `state` property of the returned subscription object to be
notified of when the subscription has been processed by the server and
all objects matching the query are available.
Creating a new subscription with the same name and query as an existing
subscription will not create a new subscription, but instead will return
an object referring to the existing sync subscription. This means that
performing the same subscription twice followed by removing it once will
result in no subscription existing.
The newly created subscription will not be reported by
`-[RLMRealm subscriptions]` or `-[RLMRealm subscriptionWithName:]` until
`state` has transitioned from `RLMSyncSubscriptionStateCreating` to any of the
other states.
@param subscriptionName The name of the subscription.
@return An object representing the newly-created subscription.
@see RLMSyncSubscription
*/
- (RLMSyncSubscription *)subscribeWithName:(nullable NSString *)subscriptionName;
/**
Subscribe to a subset of the query represented by this `RLMResults`.
Subscribing to a query asks the server to synchronize all objects to the
client which match the query, along with all objects which are reachable
from those objects via links. This happens asynchronously, and the local
client Realm may not immediately have all objects which match the query.
Observe the `state` property of the returned subscription object to be
notified of when the subscription has been processed by the server and
all objects matching the query are available.
Creating a new subscription with the same name and query as an existing
subscription will not create a new subscription, but instead will return
an object referring to the existing sync subscription. This means that
performing the same subscription twice followed by removing it once will
result in no subscription existing.
The newly created subscription will not be reported by
`-[RLMRealm subscriptions]` or `-[RLMRealm subscriptionWithName:]` until
`state` has transitioned from `RLMSyncSubscriptionStateCreating` to any of the
other states.
The number of top-level matches may optionally be limited. This limit
respects the sort and distinct order of the query being subscribed to,
if any. Please note that the limit does not count or apply to objects
which are added indirectly due to being linked to by the objects in the
subscription. If the limit is larger than the number of objects which
match the query, all objects will be included.
@param subscriptionName The name of the subscription
@param limit The maximum number of objects to include in the subscription.
@return The subscription
@see RLMSyncSubscription
*/
- (RLMSyncSubscription *)subscribeWithName:(NSString *)subscriptionName;
*/
- (RLMSyncSubscription *)subscribeWithName:(nullable NSString *)subscriptionName limit:(NSUInteger)limit;
@end
/**
Support for managing existing subscriptions to object queries in a Realm.
*/
@interface RLMRealm (SyncSubscription)
/**
Get a list of the query-based sync subscriptions made for this Realm.
This list includes all subscriptions which are currently in the states `Pending`,
`Created`, and `Error`. Newly created subscriptions which are still in the
`Creating` state are not included, and calling this immediately after calling
`-[RLMResults subscribe]` will typically not include that subscription. Similarly,
because unsubscription happens asynchronously, this may continue to include
subscriptions after `-[RLMSyncSubscription unsubscribe]` is called on them.
This method can only be called on a Realm which is using query-based sync and
will throw an exception if called on a non-synchronized or full-sync Realm.
*/
- (RLMResults<RLMSyncSubscription *> *)subscriptions;
/**
Look up a specific query-based sync subscription by name.
Subscriptions are created asynchronously, so calling this immediately after
calling `subscribeWithName:` on a `RLMResults` will typically return `nil`.
Only subscriptions which are currently in the states `Pending`, `Created`,
and `Error` can be retrieved with this method.
This method can only be called on a Realm which is using query-based sync and
will throw an exception if called on a non-synchronized or full-sync Realm.
@return The named subscription, or `nil` if no subscription exists with that name.
*/
- (nullable RLMSyncSubscription *)subscriptionWithName:(NSString *)name;
@end
NS_ASSUME_NONNULL_END
......@@ -96,6 +96,14 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property (nullable, nonatomic, readonly) NSString *identity;
/**
The user's refresh token used to access the Realm Object Server.
This is required to make HTTP requests to Realm Object Server's REST API
for functionality not exposed natively. It should be treated as sensitive data.
*/
@property (nullable, nonatomic, readonly) NSString *refreshToken;
/**
The URL of the authentication server this user will communicate with.
*/
......
......@@ -65,7 +65,6 @@ private:
- (instancetype)initWithSyncUser:(std::shared_ptr<SyncUser>)user;
- (NSURL *)defaultRealmURL;
- (std::shared_ptr<SyncUser>)_syncUser;
- (nullable NSString *)_refreshToken;
+ (void)_setUpBindingContextFactory;
@end
......
......@@ -126,6 +126,7 @@ public:
private:
struct Base {
virtual ~Base() {}
virtual void before(CollectionChangeSet const&)=0;
virtual void after(CollectionChangeSet const&)=0;
virtual void error(std::exception_ptr)=0;
......
......@@ -159,10 +159,10 @@ protected:
/// the old chunk.
///
/// \throw std::bad_alloc If insufficient memory was available.
virtual MemRef do_realloc(ref_type, const char* addr, size_t old_size, size_t new_size) = 0;
virtual MemRef do_realloc(ref_type, char* addr, size_t old_size, size_t new_size) = 0;
/// Release the specified chunk of memory.
virtual void do_free(ref_type, const char* addr) noexcept = 0;
virtual void do_free(ref_type, char* addr) noexcept = 0;
/// Map the specified \a ref to the corresponding memory
/// address. Note that if is_read_only(ref) returns true, then the
......@@ -338,7 +338,7 @@ inline MemRef Allocator::realloc_(ref_type ref, const char* addr, size_t old_siz
if (ref == m_debug_watch)
REALM_TERMINATE("Allocator watch: Ref was reallocated");
#endif
return do_realloc(ref, addr, old_size, new_size);
return do_realloc(ref, const_cast<char*>(addr), old_size, new_size);
}
inline void Allocator::free_(ref_type ref, const char* addr) noexcept
......@@ -347,7 +347,7 @@ inline void Allocator::free_(ref_type ref, const char* addr) noexcept
if (ref == m_debug_watch)
REALM_TERMINATE("Allocator watch: Ref was freed");
#endif
return do_free(ref, addr);
return do_free(ref, const_cast<char*>(addr));
}
inline void Allocator::free_(MemRef mem) noexcept
......
......@@ -21,6 +21,7 @@
#include <cstdint> // unint8_t etc
#include <vector>
#include <map>
#include <string>
#include <atomic>
......@@ -300,9 +301,9 @@ public:
protected:
MemRef do_alloc(const size_t size) override;
MemRef do_realloc(ref_type, const char*, size_t old_size, size_t new_size) override;
MemRef do_realloc(ref_type, char*, size_t old_size, size_t new_size) override;
// FIXME: It would be very nice if we could detect an invalid free operation in debug mode
void do_free(ref_type, const char*) noexcept override;
void do_free(ref_type, char*) noexcept override;
char* do_translate(ref_type) const noexcept override;
/// Returns the first section boundary *above* the given position.
......@@ -355,11 +356,104 @@ private:
ref_type ref_end;
char* addr;
};
struct Chunk {
struct Chunk { // describes a freed in-file block
ref_type ref;
size_t size;
};
// free blocks that are in the slab area are managed using the following structures:
// - FreeBlock: Placed at the start of any free space. Holds the 'ref' corresponding to
// the start of the space, and prev/next links used to place it in a size-specific
// freelist.
// - BetweenBlocks: Structure sitting between any two free OR allocated spaces.
// describes the size of the space before and after.
// Each slab (area obtained from the underlying system) has a terminating BetweenBlocks
// at the beginning and at the end of the Slab.
struct FreeBlock {
ref_type ref; // ref for this entry. Saves a reverse translate / representing links as refs
FreeBlock* prev; // circular doubly linked list
FreeBlock* next;
void clear_links() { prev = next = nullptr; }
void unlink();
};
struct BetweenBlocks { // stores sizes and used/free status of blocks before and after.
int32_t block_before_size; // negated if block is in use,
int32_t block_after_size; // positive if block is free - and zero at end
};
using FreeListMap = std::map<int, FreeBlock*>; // log(N) addressing for larger blocks
FreeListMap m_block_map;
// abstract notion of a freelist - used to hide whether a freelist
// is residing in the small blocks or the large blocks structures.
struct FreeList {
int size = 0; // size of every element in the list, 0 if not found
FreeListMap::iterator it;
bool found_something() { return size != 0; }
bool found_exact(int sz) { return size == sz; }
};
// simple helper functions for accessing/navigating blocks and betweenblocks (TM)
BetweenBlocks* bb_before(FreeBlock* entry) const {
return reinterpret_cast<BetweenBlocks*>(entry) - 1;
}
BetweenBlocks* bb_after(FreeBlock* entry) const {
auto bb = bb_before(entry);
size_t sz = bb->block_after_size;
char* addr = reinterpret_cast<char*>(entry) + sz;
return reinterpret_cast<BetweenBlocks*>(addr);
}
FreeBlock* block_before(BetweenBlocks* bb) const {
size_t sz = bb->block_before_size;
if (sz <= 0) return nullptr; // only blocks that are not in use
char* addr = reinterpret_cast<char*>(bb) - sz;
return reinterpret_cast<FreeBlock*>(addr);
}
FreeBlock* block_after(BetweenBlocks* bb) const {
if (bb->block_after_size <= 0)
return nullptr;
return reinterpret_cast<FreeBlock*>(bb + 1);
}
int size_from_block(FreeBlock* entry) const {
return bb_before(entry)->block_after_size;
}
void mark_allocated(FreeBlock* entry);
// mark the entry freed in bordering BetweenBlocks. Also validate size.
void mark_freed(FreeBlock* entry, int size);
// hook for the memory verifier in Group.
template<typename Func>
void for_all_free_entries(Func f) const;
// Main entry points for alloc/free:
FreeBlock* allocate_block(int size);
void free_block(ref_type ref, FreeBlock* addr);
// Searching/manipulating freelists
FreeList find(int size);
FreeList find_larger(FreeList hint, int size);
FreeBlock* pop_freelist_entry(FreeList list);
void push_freelist_entry(FreeBlock* entry);
void remove_freelist_entry(FreeBlock* element);
void rebuild_freelists_from_slab();
void clear_freelists();
// grow the slab area to accommodate the requested size.
// returns a free block large enough to handle the request.
FreeBlock* grow_slab_for(int request_size);
// create a single free chunk with "BetweenBlocks" at both ends and a
// single free chunk between them. This free chunk will be of size:
// slab_size - 2 * sizeof(BetweenBlocks)
FreeBlock* slab_to_entry(Slab slab, ref_type ref_start);
// breaking/merging of blocks
FreeBlock* get_prev_block_if_mergeable(FreeBlock* block);
FreeBlock* get_next_block_if_mergeable(FreeBlock* block);
// break 'block' to give it 'new_size'. Return remaining block.
// If the block is too small to split, return nullptr.
FreeBlock* break_block(FreeBlock* block, int new_size);
FreeBlock* merge_blocks(FreeBlock* first, FreeBlock* second);
// Values of each used bit in m_flags
enum {
flags_SelectBit = 1,
......@@ -425,10 +519,9 @@ private:
FeeeSpaceState m_free_space_state = free_space_Clean;
typedef std::vector<Slab> slabs;
typedef std::vector<Chunk> chunks;
using Chunks = std::map<ref_type, size_t>;
slabs m_slabs;
chunks m_free_space;
chunks m_free_read_only;
Chunks m_free_read_only;
bool m_debug_out = false;
struct hash_entry {
......@@ -440,14 +533,15 @@ private:
mutable size_t version = 1;
/// Throws if free-lists are no longer valid.
void consolidate_free_read_only();
size_t consolidate_free_read_only();
/// Throws if free-lists are no longer valid.
const chunks& get_free_read_only() const;
const Chunks& get_free_read_only() const;
/// Throws InvalidDatabase if the file is not a Realm file, if the file is
/// corrupted, or if the specified encryption key is incorrect. This
/// function will not detect all forms of corruption, though.
void validate_buffer(const char* data, size_t len, const std::string& path);
void throw_header_exception(std::string msg, const Header& header, const std::string& path);
static bool is_file_on_streaming_form(const Header& header);
/// Read the top_ref from the given buffer and set m_file_on_streaming_form
......@@ -568,6 +662,33 @@ inline size_t SlabAlloc::get_section_base(size_t index) const noexcept
return m_section_bases[index];
}
template<typename Func>
void SlabAlloc::for_all_free_entries(Func f) const
{
ref_type ref = m_baseline;
for (auto& e : m_slabs) {
BetweenBlocks* bb = reinterpret_cast<BetweenBlocks*>(e.addr);
REALM_ASSERT(bb->block_before_size == 0);
while (1) {
int size = bb->block_after_size;
f(ref, sizeof(BetweenBlocks));
ref += sizeof(BetweenBlocks);
if (size == 0) {
break;
}
if (size > 0) { // freeblock.
f(ref, size);
bb = reinterpret_cast<BetweenBlocks*>(reinterpret_cast<char*>(bb) + sizeof(BetweenBlocks) + size);
ref += size;
}
else {
bb = reinterpret_cast<BetweenBlocks*>(reinterpret_cast<char*>(bb) + sizeof(BetweenBlocks) - size);
ref -= size;
}
}
}
}
} // namespace realm