Skip to content

CBL-8595: Add URLEndpointListener to the C++ API - #736

Open
jianminzhao wants to merge 8 commits into
masterfrom
cbl-8595
Open

CBL-8595: Add URLEndpointListener to the C++ API#736
jianminzhao wants to merge 8 commits into
masterfrom
cbl-8595

Conversation

@jianminzhao

Copy link
Copy Markdown
Contributor

CBL-8265: Unit tests of class URLEndpointListener in the C++ API

Add URLEndpointListener, URLEndpointListenerConfiguration, and ListenerAuthenticator to the C++ API (include/cbl++/URLEndpointListener.hh), wrapping include/cbl/CBLURLEndpointListener.h, following the same pattern established by TLSIdentity's C++ wrapper (CBL 8594).

  • ListenerAuthenticator boxes password/certificate callbacks in a std::variant held by shared_ptr, bridging arbitrary capturing C++ callables across the C API's plain-function-pointer callback signature.
  • URLEndpointListener keeps its own copy of the configured ListenerAuthenticator so the callback's context stays alive for the listener's own lifetime, independent of the config used to construct it.
  • Adds Authenticator::certificateAuthenticator to Replicator.hh, filling a pre-existing gap (Authenticator only wrapped password/session auth).
  • Full Doxygen coverage, and a complete C++ test port (test/URLEndpointListenerTest_Cpp.cc/.hh) of URLEndpointListenerTest.cc's 21 test cases, plus completing three previously-stubbed C++ tests in TLSIdentityTest_Cpp.cc that needed this wrapper.

CBL-8265: Unit tests of class URLEndpointListener in the C++ API

Add URLEndpointListener, URLEndpointListenerConfiguration, and
ListenerAuthenticator to the C++ API (include/cbl++/URLEndpointListener.hh),
wrapping include/cbl/CBLURLEndpointListener.h, following the same pattern
established by TLSIdentity's C++ wrapper (CBL 8594).

- ListenerAuthenticator boxes password/certificate callbacks in a
  std::variant held by shared_ptr, bridging arbitrary capturing C++
  callables across the C API's plain-function-pointer callback signature.
- URLEndpointListener keeps its own copy of the configured
  ListenerAuthenticator so the callback's context stays alive for the
  listener's own lifetime, independent of the config used to construct it.
- Adds Authenticator::certificateAuthenticator to Replicator.hh, filling a
  pre-existing gap (Authenticator only wrapped password/session auth).
- Full Doxygen coverage, and a complete C++ test port
  (test/URLEndpointListenerTest_Cpp.cc/.hh) of URLEndpointListenerTest.cc's
  21 test cases, plus completing three previously-stubbed C++ tests in
  TLSIdentityTest_Cpp.cc that needed this wrapper.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an Enterprise Edition C++ wrapper for CBLURLEndpointListener (plus related configuration/authentication helpers) and ports/extends the C++ test coverage to match the existing C test suite, including previously-stubbed TLS identity handshake tests.

Changes:

  • Introduces cbl::URLEndpointListener, cbl::URLEndpointListenerConfiguration, and cbl::ListenerAuthenticator in the public C++ API.
  • Adds cbl::Authenticator::certificateAuthenticator (EE-only) to complete the Replicator C++ auth surface.
  • Adds/ports C++ unit tests for URL endpoint listeners and updates CMake/Xcode test source lists accordingly.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
test/URLEndpointListenerTest_Cpp.hh Adds a C++ test fixture and helpers for listener/replicator-based tests.
test/URLEndpointListenerTest_Cpp.cc Ports the C URLEndpointListener test suite to C++ (EE-only).
test/TLSIdentityTest_Cpp.cc Updates TLSIdentity C++ tests to use the new C++ listener wrapper and completes previously-stubbed handshake tests.
test/cmake/test_source_files.cmake Registers the new C++ listener test file in the CMake test target.
include/cbl++/URLEndpointListener.hh Adds the new public C++ API wrapper for URL endpoint listeners and listener-side authentication.
include/cbl++/TLSIdentity.hh Adds friendship needed for the new listener/authenticator wrappers to access internal adoption helpers.
include/cbl++/Replicator.hh Adds EE-only client certificate authenticator factory and includes TLSIdentity.
include/cbl++/CouchbaseLite.hh Exposes the new URLEndpointListener C++ header via the umbrella include.
CBL_C.xcodeproj/project.pbxproj Adds the new public header and test sources to Xcode build phases.
Comments suppressed due to low confidence (1)

include/cbl++/URLEndpointListener.hh:67

  • certAuthenticator(callback) accepts an empty std::function; if the listener later invokes it, this will throw std::bad_function_call (or terminate) across the C callback boundary. Validate the callback and throw a Couchbase Lite invalid-parameter error up front.
        static ListenerAuthenticator certAuthenticator(CertAuthCallback callback) {
            return ListenerAuthenticator(std::move(callback));
        }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread include/cbl++/URLEndpointListener.hh
Comment thread include/cbl++/URLEndpointListener.hh
Comment thread include/cbl++/Replicator.hh
@cbl-bot

cbl-bot commented Jul 24, 2026

Copy link
Copy Markdown

Code Coverage Results:

Branches Functions Instantiations Lines Regions
73.94 94.72 79.89 90.73 86.14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

include/cbl++/URLEndpointListener.hh:72

  • Typo in the thrown error message: "callbck" should be "callback".
        static ListenerAuthenticator certAuthenticator(CertAuthCallback callback) {
            if ( !callback ) {
                throw Error{kCBLDomain, kCBLErrorInvalidParameter, "Falsy callbck"};
            }
            return ListenerAuthenticator(std::move(callback));

Comment thread include/cbl++/URLEndpointListener.hh

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (2)

include/cbl++/URLEndpointListener.hh:61

  • Typo in the thrown error message: "callbck" should be "callback" to keep error messages readable/searchable.
                throw Error{kCBLDomain, kCBLErrorInvalidParameter, "Falsy callbck"};

include/cbl++/URLEndpointListener.hh:70

  • Typo in the thrown error message: "callbck" should be "callback" to keep error messages readable/searchable.
                throw Error{kCBLDomain, kCBLErrorInvalidParameter, "Falsy callbck"};

Comment thread include/cbl++/URLEndpointListener.hh
Comment on lines +232 to +235
/** The TLS identity used by the listener for TLS communication, or a falsy TLSIdentity if the
listener is not started, or if TLS is disabled.
@note The returned identity remains valid only until the listener is stopped or released.
Assign it to a variable of your own if you need it to outlive that. */
Comment thread include/cbl++/URLEndpointListener.hh Outdated
Comment thread test/URLEndpointListenerTest_Cpp.hh
jianminzhao and others added 2 commits July 23, 2026 22:15
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (2)

include/cbl++/URLEndpointListener.hh:123

  • ListenerAuthenticator callback is invoked from the C API; if the user-supplied std::function throws, the exception will cross a C function-pointer boundary and can terminate the process. Catch exceptions in the trampoline and return false instead of letting them escape.
        static bool _callCertAuth(void* context, CBLCert* cert) {
            return std::get<CertAuthCallback>(*(Callback*)context)(Cert(cert));
        }

include/cbl++/URLEndpointListener.hh:242

  • urls() unconditionally calls FLMutableArray_Release(flUrls), but CBLURLEndpointListener_Urls returns null when the listener isn’t started. If FLMutableArray_Release is not null-safe, this can crash. Guard the release (and return a falsy MutableArray) when flUrls is null.
        fleece::MutableArray urls() const {
            FLMutableArray flUrls = CBLURLEndpointListener_Urls(ref());
            fleece::MutableArray result(flUrls);
            FLMutableArray_Release(flUrls);
            return result;

Comment thread include/cbl++/URLEndpointListener.hh Outdated
Comment on lines +117 to +119
static bool _callPasswordAuth(void* context, FLString username, FLString password) {
return std::get<PasswordAuthCallback>(*(Callback*)context)(slice(username), slice(password));
}
Comment thread test/URLEndpointListenerTest_Cpp.hh Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

include/cbl++/URLEndpointListener.hh:244

  • The tlsIdentity() doc comment is internally contradictory: it says the returned identity is only valid until the listener is stopped/released, but also says assigning it to a variable can outlive that. The C API contract is that the identity is valid until stop/release, and can be kept longer by retaining; in C++ that corresponds to storing the returned TLSIdentity (it retains).
        /** The TLS identity used by the listener for TLS communication, or a falsy TLSIdentity if the
            listener is not started, or if TLS is disabled.
            @note The returned identity remains valid only until the listener is stopped or released.
                  Assign it to a variable of your own if you need it to outlive that. */

Comment thread include/cbl++/Replicator.hh
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Comment thread test/URLEndpointListenerTest_Cpp.cc Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

include/cbl++/URLEndpointListener.hh:244

  • The tlsIdentity() doc comment says the returned identity is only valid until the listener is stopped or released, but the C++ wrapper constructor retains the CBLTLSIdentity (via RefCounted), so a stored return value can outlive the listener. The note should match the actual ownership/lifetime semantics to avoid misleading API users.
        /** The TLS identity used by the listener for TLS communication, or a falsy TLSIdentity if the
            listener is not started, or if TLS is disabled.
            @note The returned identity remains valid only until the listener is stopped or released.
                  Assign it to a variable of your own if you need it to outlive that. */

Comment thread test/URLEndpointListenerTest_Cpp.cc Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

include/cbl++/URLEndpointListener.hh:252

  • urls() releases flUrls unconditionally even though the C API documents that CBLURLEndpointListener_Urls can return NULL when the listener isn’t started. Guard the release so this wrapper doesn’t rely on FLMutableArray_Release(NULL) being safe, and explicitly return a falsy MutableArray when flUrls is null.
        fleece::MutableArray urls() const {
            FLMutableArray flUrls = CBLURLEndpointListener_Urls(ref());
            fleece::MutableArray result(flUrls);
            FLMutableArray_Release(flUrls);
            return result;

include/cbl++/URLEndpointListener.hh:245

  • The doc comment for tlsIdentity() says the returned identity is only valid until the listener is stopped/released, but this wrapper constructs TLSIdentity(...) using the retaining wrapper constructor, so callers can keep the returned TLSIdentity alive independently of the listener. Please update the note to match the actual C++ lifetime semantics.
        /** The TLS identity used by the listener for TLS communication, or a falsy TLSIdentity if the
            listener is not started, or if TLS is disabled.
            @note The returned identity remains valid only until the listener is stopped or released.
                  Assign it to a variable of your own if you need it to outlive that. */
        TLSIdentity tlsIdentity() const  {return TLSIdentity(CBLURLEndpointListener_TLSIdentity(ref()));}

@jianminzhao
jianminzhao requested a review from pasin July 25, 2026 01:57
Comment thread include/cbl++/Base.hh
@param className The class the trampoline belongs to, used only for the log message.
@param what A short description of the operation, used only for the log message. */
template <class Fn>
inline bool invokeSafely(const char* className, const char* what, Fn&& fn) noexcept {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the method name and where it is defined, it's a bit weird that the default error domain is Network. Wondering if the error domain should be passed in?

using CertAuthCallback = std::function<bool(const Cert& cert)>;

/** Creates an empty (null) authenticator. */
ListenerAuthenticator() = default;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be discarded?

// internal::invokeSafely helper) -- so any exception the user's callback throws is caught,
// logged, and turned into a plain authentication failure (false) instead.
static bool _callPasswordAuth(void* context, FLString username, FLString password) noexcept {
return internal::invokeSafely("ListenerAuthenticator", "passwordAuthCallback", [&] {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error should be logged in kCBLLogDomainListener domain instead of the default network domain.

}

static bool _callCertAuth(void* context, CBLCert* cert) noexcept {
return internal::invokeSafely("ListenerAuthenticator", "certAuthCallback", [&] {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error should be logged in kCBLLogDomainListener domain instead of the default network domain.

listenerConfig.disableTLS = false;

SECTION("Self-signed Cert") {
listenerConfig.tlsIdentity = createTLSIdentity(true, withExternalKey);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to set identityLabelsToDelete or is it set in createTLSIdentity() function?

clientEndpoint(listenerConfig, listener));
config.acceptOnlySelfSignedServerCertificate = true;

TLSIdentity clientIdentity = createTLSIdentity(false, withExternalKey);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above comment, do we need to set identityLabelsToDelete?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants