Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes to support Edge Server [API] #2202

Open
wants to merge 62 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
f41c5eb
Replicator's DBAccess now uses a database pool
snej Nov 15, 2024
2c7e048
more db pool in replicator
snej Nov 18, 2024
7a7aec8
c4ReplicatorImpl now supports DatabasePool
snej Nov 18, 2024
61caeb4
build fixes for CE and Xcode
snej Nov 18, 2024
e05d434
Changes for EdgeServer
snej Aug 20, 2024
07ed8f2
SyncListener creates Replicator w/DatabasePool
snej Nov 19, 2024
23afbe7
SyncListener uses Task. Ensure Tasks get stopped.
snej Nov 20, 2024
160d290
DatabasePool logging
snej Nov 20, 2024
1acdfa2
Support v3 protocol in P2P sync
snej Nov 20, 2024
3f7e1b8
CppTests pass again
snej Nov 20, 2024
562ea12
Make WITH_ERROR info more visible in test output
snej Nov 20, 2024
25f0964
DatabasePool improvements (Transaction)
snej Nov 20, 2024
1b92967
Request/Response improvements
snej Nov 20, 2024
a258028
clang-format
snej Nov 20, 2024
058dbc9
C4Listener: Per-database configuration
snej Nov 21, 2024
8adef9c
REST: Refactoring. Moved more code into EdgeServer.
snej Nov 23, 2024
87b6d25
DatabasePool: Proper error if pool is read-only
snej Nov 25, 2024
6a4a2ff
Internal API to start replicator with DatabasePool
snej Nov 25, 2024
e9e3561
Added HTTPListener::Task::listed()
snej Dec 2, 2024
3628503
Fixed uninitialized ptr in BorrowedCollection struct
snej Dec 2, 2024
3c7776d
Fixed misleading comment in c4ReplicatorTypes.h
snej Dec 3, 2024
3911fdc
C4Collection::createIndex() now returns bool [API]
snej Dec 3, 2024
98c5743
Added missing include to HTTPListener.hh
snej Dec 3, 2024
2492748
Added HTTPListener::findMatchingSyncProtocol()
snej Dec 4, 2024
a944ee2
Fixed warnings, reformatted
snej Dec 4, 2024
55cd859
Fixed nasty use-after-free bug in Headers move-assignment
snej Dec 4, 2024
75acf1f
Fixing Xcode and Windows builds
snej Dec 5, 2024
156a28f
Resolved GCC warnings w/CBL_CORE_API
snej Dec 5, 2024
344a62f
cleanup
snej Dec 5, 2024
2e94064
Simplified implementation of Headers
snej Dec 5, 2024
c9690ca
TCPSocket: Don't log multiple identical errors
snej Dec 5, 2024
fafd07e
Avoid GCC multi-line comment warning in c4ReplicatorTypes
snej Dec 5, 2024
28d25d4
C4Listener: Made stopping more robust
snej Dec 6, 2024
e6a4b2b
HTTPListener: use task_id not pid in Task JSON description
snej Dec 9, 2024
93a773e
C4DocEnumerator: Added `startKey` option [API]
snej Dec 9, 2024
bd30be1
Attempt to fix MSVC link error w/ kC4Cpp_DefaultLog
snej Dec 11, 2024
5dd8ae7
Refactor the CMake project
borrrden Dec 11, 2024
ff3a540
Fixed UBSan error calling C4Listener::shareDB()
snej Dec 11, 2024
ba39e89
Added C4Collection::getPurgeCount() [API]
snej Dec 20, 2024
e093cb2
HTTPListener: use "age_sece" not "started_on" in _active_tasks
snej Dec 20, 2024
b5c3dbf
Switch to official cbdeps build of icu
borrrden Dec 20, 2024
7745991
CMake: Fixed LITECORE_SANITIZE
snej Dec 21, 2024
9a38075
Removed unnecessary/conflicting change in Replicator.cc
snej Jan 7, 2025
15babd1
Some HTTP fixes
snej Jan 7, 2025
b74628b
effin' clang-format again
snej Jan 8, 2025
b2f9c7f
Ensure sequence indexes are created when DBAccess starts
snej Jan 8, 2025
1c575cb
Updated fleece
snej Jan 8, 2025
d9971cb
Fix Android build issue
borrrden Jan 9, 2025
8f1a22a
Add missing header in SyncListenerTest
borrrden Jan 9, 2025
9b20020
Fixed DatabasePool deadlock
snej Jan 9, 2025
dd652d3
DatabasePool allows multiple borrows of r/o db on the same thread
snej Jan 10, 2025
fa67863
Added missing includes to DatabasePool
snej Jan 10, 2025
d05c1af
DatabasePool: Updated doc-comments
snej Jan 10, 2025
0be731e
Bug-fix for DatabaseRegistry::borrowCollection [CBL-6637]
snej Jan 15, 2025
d162704
Body::bodyAsJSON() didn't understand charset on Content-Type
snej Jan 15, 2025
eb0ebe7
Downgrade warnings when peer closes a socket
snej Jan 15, 2025
eb37714
formatting in DatabaseRegistry.cc
snej Jan 15, 2025
b0da73d
Updated Fleece now that its PR 246 was merged
snej Jan 16, 2025
7adf7f1
Fixed thread-safety issue with simultaneous TLS handshakes [CBL-6640]
snej Jan 17, 2025
0c6f6e2
Better TLS thread-safety fix [CBL-6640][CBL-6649]
snej Jan 17, 2025
f60d148
mbedTLS logs should never be warnings or errors
snej Jan 17, 2025
219a0c1
Update mbedTLS to latest
snej Jan 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions C/Cpp_include/c4Base.hh
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,6 @@ namespace litecore {
class PublicKey;
} // namespace crypto

namespace REST {
class Listener;
class RESTListener;
} // namespace REST

namespace websocket {
class WebSocket;
}
Expand Down
4 changes: 3 additions & 1 deletion C/Cpp_include/c4Collection.hh
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ struct C4Collection
virtual uint64_t getDocumentCount() const = 0;

virtual C4SequenceNumber getLastSequence() const = 0;
virtual uint64_t getPurgeCount() const = 0;

C4ExtraInfo& extraInfo() noexcept { return _extraInfo; }

Expand Down Expand Up @@ -93,7 +94,8 @@ struct C4Collection
/// Same as the C4Database method, but the query will refer to this collection by default.
Retained<C4Query> newQuery(C4QueryLanguage language, slice queryExpr, int* outErrorPos) const;

virtual void createIndex(slice name, slice indexSpec, C4QueryLanguage indexLanguage, C4IndexType indexType,
/// Returns true if it created or replaced the index, false if it already exists.
virtual bool createIndex(slice name, slice indexSpec, C4QueryLanguage indexLanguage, C4IndexType indexType,
const C4IndexOptions* C4NULLABLE indexOptions = nullptr) = 0;

virtual Retained<C4Index> getIndex(slice name) = 0;
Expand Down
10 changes: 6 additions & 4 deletions C/Cpp_include/c4Database.hh
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ struct C4Database
/** Attempts to discover and verify the named extension in the provided path */
static void enableExtension(slice name, slice path);

static bool exists(slice name, slice inDirectory);
static void copyNamed(slice sourcePath, slice destinationName, const Config&);
static bool deleteNamed(slice name, slice inDirectory);
static bool deleteAtPath(slice path);
static bool exists(slice name, slice inDirectory);
static void copyNamed(slice sourcePath, slice destinationName, const Config&);
[[nodiscard]] static bool deleteNamed(slice name, slice inDirectory);
[[nodiscard]] static bool deleteAtPath(slice path);

static Retained<C4Database> openNamed(slice name, const Config&);

Expand Down Expand Up @@ -184,6 +184,8 @@ struct C4Database
db->endTransaction(false);
}

bool isActive() const noexcept { return _db != nullptr; }

~Transaction() {
if ( _db ) _db->endTransaction(false);
}
Expand Down
7 changes: 7 additions & 0 deletions C/Cpp_include/c4DocEnumerator.hh
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ struct C4DocEnumerator
explicit C4DocEnumerator(C4Collection* collection,
const C4EnumeratorOptions& options = kC4DefaultEnumeratorOptions);

/// Creates an enumerator on a collection, beginning at `startKey`.
/// (This means that if the order is descending, `startKey` will be the maximum key.)
/// If `startKey` is null, it's ignored and all documents are returned.
/// You must first call \ref next to step to the first document.
explicit C4DocEnumerator(C4Collection* collection, slice startKey,
const C4EnumeratorOptions& options = kC4DefaultEnumeratorOptions);

/// Creates an enumerator on a collection, ordered by sequence.
/// You must first call \ref next to step to the first document.
explicit C4DocEnumerator(C4Collection* collection, C4SequenceNumber since,
Expand Down
74 changes: 55 additions & 19 deletions C/Cpp_include/c4Listener.hh
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#pragma once
#include "c4Base.hh"
#include "c4ListenerTypes.h"
#include "fleece/FLBase.h"
#include "fleece/InstanceCounted.hh"
#include <vector>

Expand All @@ -24,41 +25,76 @@ C4_ASSUME_NONNULL_BEGIN
// the dynamic library only exports the C API.
// ************************************************************************

namespace litecore::REST {
class HTTPListener;
}

/** A lightweight server that shares databases over the network for replication.
@note This class is not ref-counted. Instances must be explicitly deleted/destructed. */
struct C4Listener final
: public fleece::InstanceCounted
, C4Base {
static C4ListenerAPIs availableAPIs();

explicit C4Listener(C4ListenerConfig config);

~C4Listener() override;

bool shareDB(slice name, C4Database* db);

/// Constructor. Starts the listener (asynchronously) but does not share any databases.
explicit C4Listener(C4ListenerConfig const& config);

~C4Listener() noexcept override;

/// Stops the listener. If you don't call this, the destructor will do it for you.
C4Error stop() noexcept;

/// Shares a database, and its default collection.
/// @param name The URI name (first path component) in the HTTP API.
/// If `nullslice`, the C4Database's name will be used (possibly URL-escaped).
/// The name may not include '/', '.', control characters, or non-ASCII characters.
/// @param db The database to share. On success this instance is now managed by the Listener
/// and should not be used again by the caller.
/// @param dbConfig Optional configuration for this database. Overrides the C4ListenerConfig.
/// @returns True on success, false if the name is already in use.
[[nodiscard]] bool shareDB(slice name, C4Database* db,
C4ListenerDatabaseConfig const* C4NULLABLE dbConfig = nullptr);

/// Stops sharing a database. `db` need not be the exact instance that was registered;
/// any instance on the same database file will work.
bool unshareDB(C4Database* db);

bool shareCollection(slice name, C4Collection* coll);

bool unshareCollection(slice name, C4Collection* coll);

/// Adds a collection to be shared.
/// @note A database's default collection is automatically shared.
/// @param name The URI name the database is registered by.
/// @param collection The collection instance to share.
/// @returns True on success, false if `name` is not registered. */
[[nodiscard]] bool shareCollection(slice name, C4Collection* collection);

/// Stops sharing a collection.
/// @note Call this after \ref registerDatabase if you don't want to share the default collection.
/// @param name The URI name the database is registered by.
/// @param collection The collection instance.
/// @returns True on success, false if the database name or collection is not registered. */
bool unshareCollection(slice name, C4Collection* collection);

/// The TCP port number for incoming connections.
[[nodiscard]] uint16_t port() const;

/// Returns first the number of connections, and second the number of active connections.
[[nodiscard]] std::pair<unsigned, unsigned> connectionStatus() const;

std::vector<std::string> URLs(C4Database* C4NULLABLE db, C4ListenerAPIs api) const;
/// Returns the URL(s) of a database being shared, or of the root.
/// The URLs will differ only in their hostname -- there will be one for each IP address or known
/// hostname of the computer, or of the network interface.
[[nodiscard]] std::vector<std::string> URLs(C4Database* C4NULLABLE db) const;

static std::string URLNameFromPath(slice path);
/// A convenience that, given a filesystem path to a database, returns the database name
/// for use in an HTTP URI path.
[[nodiscard]] static std::string URLNameFromPath(slice path);

C4Listener(const C4Listener&) = delete;

// internal use only
C4Listener(C4ListenerConfig const& config, Retained<litecore::REST::HTTPListener> impl);

private:
// For some reason, MSVC on Jenkins will not compile this with noexcept (everything else will)
C4Listener(C4Listener&&); // NOLINT(performance-noexcept-move-constructor)
C4Listener(C4Listener&&) noexcept;

Retained<litecore::REST::RESTListener> _impl;
C4ListenerHTTPAuthCallback C4NULLABLE _httpAuthCallback;
void* C4NULLABLE _callbackContext;
Retained<litecore::REST::HTTPListener> _impl;
};

C4_ASSUME_NONNULL_END
12 changes: 7 additions & 5 deletions C/Cpp_include/c4Query.hh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <memory>
#include <mutex>
#include <set>
#include <string>
#include <utility>

C4_ASSUME_NONNULL_BEGIN
Expand Down Expand Up @@ -45,11 +46,12 @@ struct C4Query final
int* C4NULLABLE outErrorPos);

unsigned columnCount() const noexcept;
slice columnTitle(unsigned col) const;
slice columnTitle(unsigned col) const LIFETIMEBOUND;
alloc_slice explain() const;

alloc_slice parameters() const noexcept;
void setParameters(slice parameters);
const std::set<std::string>& parameterNames() const noexcept LIFETIMEBOUND;
alloc_slice parameters() const noexcept;
void setParameters(slice parameters);

alloc_slice fullTextMatched(const C4FullTextMatch&);

Expand All @@ -62,8 +64,8 @@ struct C4Query final
[[nodiscard]] int64_t rowCount() const;
void seek(int64_t rowIndex);

[[nodiscard]] FLArrayIterator columns() const;
[[nodiscard]] FLValue column(unsigned i) const;
[[nodiscard]] FLArrayIterator columns() const LIFETIMEBOUND;
[[nodiscard]] FLValue column(unsigned i) const LIFETIMEBOUND;

[[nodiscard]] unsigned fullTextMatchCount() const;
[[nodiscard]] C4FullTextMatch fullTextMatch(unsigned i) const;
Expand Down
14 changes: 7 additions & 7 deletions C/c4Base.cc
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ using namespace litecore;


extern "C" {
CBL_CORE_API std::atomic_int gC4ExpectExceptions;
bool C4ExpectingExceptions();
std::atomic_int gC4ExpectExceptions;
bool C4ExpectingExceptions();

bool C4ExpectingExceptions() { return gC4ExpectExceptions > 0; } // LCOV_EXCL_LINE
}
Expand Down Expand Up @@ -174,11 +174,11 @@ C4StringResult c4log_binaryFilePath(void) C4API {
}

// NOLINTBEGIN(misc-misplaced-const,cppcoreguidelines-interfaces-global-init)
CBL_CORE_API C4LogDomain const kC4DefaultLog = (C4LogDomain)&kC4Cpp_DefaultLog;
CBL_CORE_API C4LogDomain const kC4DatabaseLog = (C4LogDomain)&DBLog;
CBL_CORE_API C4LogDomain const kC4QueryLog = (C4LogDomain)&QueryLog;
CBL_CORE_API C4LogDomain const kC4SyncLog = (C4LogDomain)&SyncLog;
CBL_CORE_API C4LogDomain const kC4WebSocketLog = (C4LogDomain)&websocket::WSLogDomain;
C4LogDomain const kC4DefaultLog = (C4LogDomain)&kC4Cpp_DefaultLog;
C4LogDomain const kC4DatabaseLog = (C4LogDomain)&DBLog;
C4LogDomain const kC4QueryLog = (C4LogDomain)&QueryLog;
C4LogDomain const kC4SyncLog = (C4LogDomain)&SyncLog;
C4LogDomain const kC4WebSocketLog = (C4LogDomain)&websocket::WSLogDomain;

// NOLINTEND(misc-misplaced-const,cppcoreguidelines-interfaces-global-init)

Expand Down
3 changes: 1 addition & 2 deletions C/c4BlobStore.cc
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,7 @@ C4BlobStore::C4BlobStore(slice dirPath, C4DatabaseFlags flags, const C4Encryptio
FilePath dir(_dirPath, "");
if ( dir.exists() ) {
dir.mustExistAsDir();
} else {
if ( !(flags & kC4DB_Create) ) error::_throw(error::NotFound);
} else if ( !(flags & kC4DB_ReadOnly) ) {
dir.mkdir();
}
}
Expand Down
2 changes: 1 addition & 1 deletion C/c4Certificate.cc
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ using namespace litecore::crypto;

#ifdef COUCHBASE_ENTERPRISE

CBL_CORE_API const C4CertIssuerParameters kDefaultCertIssuerParameters = {
const C4CertIssuerParameters kDefaultCertIssuerParameters = {
CertSigningRequest::kOneYear, C4STR("1"), -1, false, true, true, true};


Expand Down
4 changes: 2 additions & 2 deletions C/c4Database.cc
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ using namespace fleece;
using namespace litecore;


CBL_CORE_API const char* const kC4DatabaseFilenameExtension = ".cblite2";
const char* const kC4DatabaseFilenameExtension = ".cblite2";

CBL_CORE_API C4StorageEngine const kC4SQLiteStorageEngine = "SQLite";
C4StorageEngine const kC4SQLiteStorageEngine = "SQLite";

C4EncryptionKey C4EncryptionKeyFromPassword(slice password, C4EncryptionAlgorithm alg) {
C4EncryptionKey key;
Expand Down
64 changes: 35 additions & 29 deletions C/c4DocEnumerator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,34 +24,36 @@ using namespace litecore;

#pragma mark - DOC ENUMERATION:

CBL_CORE_API const C4EnumeratorOptions kC4DefaultEnumeratorOptions = {kC4IncludeNonConflicted | kC4IncludeBodies};
const C4EnumeratorOptions kC4DefaultEnumeratorOptions = {kC4IncludeNonConflicted | kC4IncludeBodies};

static RecordEnumerator::Options recordOptions(const C4EnumeratorOptions& c4options, slice startKey) {
RecordEnumerator::Options options;
if ( c4options.flags & kC4Descending ) options.sortOption = kDescending;
else if ( c4options.flags & kC4Unsorted )
options.sortOption = kUnsorted;
options.includeDeleted = (c4options.flags & kC4IncludeDeleted) != 0;
options.onlyConflicts = (c4options.flags & kC4IncludeNonConflicted) == 0;
if ( (c4options.flags & kC4IncludeBodies) == 0 ) options.contentOption = kMetaOnly;
else
options.contentOption = kEntireBody;
options.startKey = startKey;
return options;
}

static RecordEnumerator::Options recordOptions(const C4EnumeratorOptions& c4options, C4SequenceNumber since) {
auto options = recordOptions(c4options, nullslice);
options.minSequence = since + 1;
return options;
}

class C4DocEnumerator::Impl
: public RecordEnumerator
, public fleece::InstanceCounted {
, public InstanceCounted {
public:
Impl(C4Collection* collection, sequence_t since, const C4EnumeratorOptions& options)
: RecordEnumerator(asInternal(collection)->keyStore(), since, recordOptions(options))
Impl(C4Collection* collection, const C4EnumeratorOptions& c4Options, const RecordEnumerator::Options& options)
: RecordEnumerator(asInternal(collection)->keyStore(), options)
, _collection(asInternal(collection))
, _options(options) {}

Impl(C4Collection* collection, const C4EnumeratorOptions& options)
: RecordEnumerator(asInternal(collection)->keyStore(), recordOptions(options))
, _collection(asInternal(collection))
, _options(options) {}

static RecordEnumerator::Options recordOptions(const C4EnumeratorOptions& c4options) {
RecordEnumerator::Options options;
if ( c4options.flags & kC4Descending ) options.sortOption = kDescending;
else if ( c4options.flags & kC4Unsorted )
options.sortOption = kUnsorted;
options.includeDeleted = (c4options.flags & kC4IncludeDeleted) != 0;
options.onlyConflicts = (c4options.flags & kC4IncludeNonConflicted) == 0;
if ( (c4options.flags & kC4IncludeBodies) == 0 ) options.contentOption = kMetaOnly;
else
options.contentOption = kEntireBody;
return options;
}
, _c4Options(c4Options) {}

Retained<C4Document> getDoc() {
if ( !hasRecord() ) return nullptr;
Expand All @@ -62,7 +64,8 @@ class C4DocEnumerator::Impl
if ( !this->hasRecord() ) return false;

revid vers(record().version());
if ( (_options.flags & kC4IncludeRevHistory) && vers.isVersion() ) _docRevID = vers.asVersionVector().asASCII();
if ( (_c4Options.flags & kC4IncludeRevHistory) && vers.isVersion() )
_docRevID = vers.asVersionVector().asASCII();
else
_docRevID = vers.expanded();

Expand All @@ -78,15 +81,18 @@ class C4DocEnumerator::Impl

private:
litecore::CollectionImpl* _collection;
C4EnumeratorOptions const _options;
C4EnumeratorOptions const _c4Options;
alloc_slice _docRevID;
};

C4DocEnumerator::C4DocEnumerator(C4Collection* collection, C4SequenceNumber since, const C4EnumeratorOptions& options)
: _impl(new Impl(collection, since, options)) {}

C4DocEnumerator::C4DocEnumerator(C4Collection* collection, const C4EnumeratorOptions& options)
: _impl(new Impl(collection, options)) {}
: C4DocEnumerator(collection, nullslice, options) {}

C4DocEnumerator::C4DocEnumerator(C4Collection* collection, slice startKey, const C4EnumeratorOptions& options)
: _impl(new Impl(collection, options, recordOptions(options, startKey))) {}

C4DocEnumerator::C4DocEnumerator(C4Collection* collection, C4SequenceNumber since, const C4EnumeratorOptions& options)
: _impl(new Impl(collection, options, recordOptions(options, since))) {}

#ifndef C4_STRICT_COLLECTION_API
C4DocEnumerator::C4DocEnumerator(C4Database* database, const C4EnumeratorOptions& options)
Expand Down
Loading
Loading